Skip to content
CampusEduX

Configuration · Lesson 23 of 95

@Value Annotation

Learn the @Value annotation in Spring Boot: read properties, set defaults, use SpEL and inject lists into constructors, with a pharmacy example.

8 min read

A pharmacy shows the delivery charge on a board at the counter. If the charge changes from 30 rupees to 40, the pharmacist changes the board, not the medicine shelves. Your code should work the same way. The number lives in a settings file, and the code just asks for it.

The @Value annotation is the simplest way to ask. You write the name of a setting above a field or constructor parameter, and Spring fills in the value before your bean is used.

What is the @Value annotation?

Think of a runner in a hospital who fetches one item from a store room when the ward asks for it. The ward names the item, the runner brings it, and the ward never walks to the store room. Spring is the runner, the settings files are the store room, and @Value is the request slip.

Spring converts the text in the file into the type you declared. A setting 30 becomes an int. The text true becomes a boolean. A comma separated line becomes a List<String>.

Why is it used?

  • No hard-coded numbers. A delivery fee inside a Java class means a rebuild every time it changes.
  • Simple for one or two settings. You do not need to write a whole class just to read a shop name.
  • Defaults are built in. You can say what to use when the key is missing.
  • Type conversion is free. Spring turns text into numbers, booleans and lists for you.

For a group of many related settings, the better tool is @ConfigurationProperties, which comes in the next guide of this course. Use @Value when you need a few single values.

How it works

While Spring creates your bean, it looks at every @Value and asks the Environment for the key. The Environment has already loaded your application.properties file, environment variables and command line arguments.

text
application.properties | v +-------------------+ | Environment | | pharmacy.name | | pharmacy.delivery | +-------------------+ | v ${pharmacy.name} +-------------------+ | Spring creates | | PharmacyController| +-------------------+ | v value converted and injected

The placeholder ${pharmacy.name} is replaced with the real text, converted to the target type, and passed to the constructor. If the key is missing and no default is given, the bean cannot be created and the application stops during startup. That early failure is good. You learn about a missing setting on the first run, not when a customer places an order.

The Three Forms of the @Value Annotation

FormMeaningExample
PlaceholderRead a key${pharmacy.name}
Placeholder with defaultUse a fallback if the key is missing${pharmacy.tagline:Stay well}
ExpressionCalculate a value with SpEL#{2 * 30}

The text after the first colon in a placeholder is the default. You can also mix the two forms, as in #{${pharmacy.delivery-fee} * 2}. Spring first replaces the placeholder with the number, then calculates the expression.

Lists deserve a small note. A comma separated line is split at each comma and the spaces around each item are trimmed by the conversion. If an item itself contains a comma, this simple form cannot hold it, and you should move to a properties class.

Real-Life Example

GreenLeaf Pharmacy delivers medicines from three branches. The delivery fee, the shop name, whether the shop is open and the list of branches are all settings. The express fee is always double the normal fee, so the owner wants it calculated, not typed twice. There is also a tagline that is optional: if nobody writes one, a friendly default should appear.

Code Example

Let's build the GreenLeaf info service. It reads five settings, calculates one, and defaults one.

File: pom.xml

xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.1.1</version> <relativePath/> </parent> <groupId>com.greenleaf</groupId> <artifactId>pharmacy</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>21</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webmvc</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

File: application.properties in src/main/resources

properties
pharmacy.name=GreenLeaf Pharmacy pharmacy.delivery-fee=30 pharmacy.open=true pharmacy.branches=Pune,Nashik,Nagpur

File: PharmacyApplication.java in package com.greenleaf.pharmacy

java
package com.greenleaf.pharmacy; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class PharmacyApplication { public static void main(String[] args) { SpringApplication.run(PharmacyApplication.class, args); } }

File: PharmacyController.java in package com.greenleaf.pharmacy

java
package com.greenleaf.pharmacy; import java.util.List; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class PharmacyController { record Info(String name, int deliveryFee, int expressFee, boolean open, List<String> branches, String tagline) {} private final Info info; public PharmacyController( @Value("${pharmacy.name}") String name, @Value("${pharmacy.delivery-fee}") int deliveryFee, @Value("#{${pharmacy.delivery-fee} * 2}") int expressFee, @Value("${pharmacy.open}") boolean open, @Value("${pharmacy.branches}") List<String> branches, @Value("${pharmacy.tagline:Your health, our care}") String tagline) { this.info = new Info(name, deliveryFee, expressFee, open, branches, tagline); } @GetMapping("/pharmacy") public Info pharmacy() { return info; } }

Run it and call the endpoint:

bash
./mvnw spring-boot:run curl http://localhost:8080/pharmacy

Output:

json
{ "name": "GreenLeaf Pharmacy", "deliveryFee": 30, "expressFee": 60, "open": true, "branches": ["Pune", "Nashik", "Nagpur"], "tagline": "Your health, our care" }

The real reply is on one line; it is spaced out here for reading.

Code Explained

  • ${pharmacy.name} reads the key from application.properties and injects the text.
  • int deliveryFee makes Spring convert the text 30 to a number. If the file said thirty, startup would fail.
  • #{${pharmacy.delivery-fee} * 2} first resolves the placeholder and then multiplies, so the express fee is 60. There is no second setting to keep in sync.
  • The comma separated line for pharmacy.branches becomes a List<String> with three items.
  • pharmacy.tagline does not exist in the file. The text after the colon is used instead, so the reply shows the default tagline.
  • The values are taken through the constructor. That keeps the fields final and makes the class easy to test.

We also removed pharmacy.name from the file and started the app again. It refused to start, and the error said Could not resolve placeholder 'pharmacy.name'. That is the behaviour you want for a required setting.

Field Injection or Constructor Injection

You will often see @Value on a private field. It works, but the field cannot be final and a unit test has to use reflection to set it. Putting @Value on constructor parameters, as above, avoids both problems. In a plain test you simply call new PharmacyController("Test", 10, 20, true, List.of(), "hi").

Common Mistakes

  • No default for an optional setting. The app will not start if the key is missing. Add :default when the value is truly optional.
  • Typing the key wrongly. @ConfigurationProperties accepts several spellings of one key, but @Value does not. Write the key in your annotation exactly as it appears in the file.
  • Using `@Value` on a static field. Spring does not inject static fields.
  • Using it for large groups. Twenty @Value lines in one class are hard to read and hard to check. Use a properties class instead.

Interview Questions

What is the difference between `${...}` and `#{...}` in `@Value`?

Ans:${...} is a property placeholder that reads a configuration key. #{...} is a Spring Expression Language expression that Spring evaluates.

How do you give a default value?

Ans:Write it after a colon, such as ${pharmacy.tagline:Stay well} inside the annotation.

What happens if the key is missing and there is no default?

Ans:Spring cannot create the bean and startup fails with a message that the placeholder could not be resolved.

Key Points to Remember

  • @Value("${key}") injects one setting from the Environment.
  • Add :default after the key to make a setting optional.
  • #{...} runs an expression, and it can contain a placeholder.
  • Spring converts text to int, boolean, List and many other types.
  • Prefer constructor parameters over private fields.
  • For groups of settings, use @ConfigurationProperties instead.

Frequently Asked Questions

Can the @Value annotation read environment variables?

Yes. The Environment includes them, so ${JAVA_HOME} or a variable you set yourself can be read the same way.

Can I change a @Value after the app has started?

No. The value is injected once when the bean is created. To pick up a new value, restart the app.

Is @Value the same as @Autowired?

No. @Autowired injects another bean. @Value injects a plain value such as text, a number or a list.

Can I use @Value in any class?

Only in classes that Spring manages, such as a controller, service or component. A class you create with new gets nothing injected.

Practice Problems

Try each problem on your own first. Both use the same pom.xml as the GreenLeaf code above; only change the groupId and artifactId.

Easy: Cinema Notice Board

Star Talkies wants a GET /board endpoint that returns one line of text. The cinema name and the number of screens come from application.properties. The late show time is optional: when the key cinema.late-show is missing, show 10:30 PM.

Show answer
Two keys are in the file, and the third is missing on purpose, so the default in the placeholder is used.

File: application.properties in src/main/resources

properties
cinema.name=Star Talkies cinema.screens=4

File: BoardApplication.java in package com.startalkies.board

java
package com.startalkies.board; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class BoardApplication { public static void main(String[] args) { SpringApplication.run(BoardApplication.class, args); } }

File: BoardController.java in package com.startalkies.board

java
package com.startalkies.board; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class BoardController { private final String line; public BoardController( @Value("${cinema.name}") String name, @Value("${cinema.screens}") int screens, @Value("${cinema.late-show:10:30 PM}") String lateShow) { this.line = name + ", " + screens + " screens, " + lateShow; } @GetMapping("/board") public String board() { return line; } }

curl http://localhost:8080/board prints:

text
Star Talkies, 4 screens, 10:30 PM

Medium: Weekend Price and Snack Count

Star Talkies charges the normal ticket price on weekdays and adds a surcharge on weekends. Keep cinema.ticket-price=200, cinema.weekend-surcharge=50 and cinema.snacks=Popcorn, Nachos, Samosa in the file. Build GET /prices that returns the weekday price, the weekend price (calculated, not typed) and the number of snacks on the menu.

Show answer
The weekend price is a SpEL sum of two placeholders. Spring splits the snack line at the commas and trims the spaces, so the list has three items.

File: application.properties in src/main/resources

properties
cinema.ticket-price=200 cinema.weekend-surcharge=50 cinema.snacks=Popcorn, Nachos, Samosa

File: PricesApplication.java in package com.startalkies.prices

java
package com.startalkies.prices; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class PricesApplication { public static void main(String[] args) { SpringApplication.run(PricesApplication.class, args); } }

File: PricesController.java in package com.startalkies.prices

java
package com.startalkies.prices; import java.util.List; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class PricesController { record Prices(int weekday, int weekend, int snackCount, List<String> snacks) {} private final Prices prices; public PricesController( @Value("${cinema.ticket-price}") int weekday, @Value("#{${cinema.ticket-price} + ${cinema.weekend-surcharge}}") int weekend, @Value("${cinema.snacks}") List<String> snacks) { this.prices = new Prices(weekday, weekend, snacks.size(), snacks); } @GetMapping("/prices") public Prices prices() { return prices; } }

curl http://localhost:8080/prices prints:

json
{ "weekday": 200, "weekend": 250, "snackCount": 3, "snacks": ["Popcorn", "Nachos", "Samosa"] }

curl prints the JSON on one line; it is spaced out here for reading.