Appearance
Spring Boot: Dynamically Initialized Beans | @Value Annotation
Real World Analogy
Think of a power outlet. Your appliance (the Java class) does not care whether the power comes from a solar panel, a generator, or the city grid. It simply plugs into the outlet and gets the power it needs. The @Value annotation is that outlet — it connects a field in your class to an external source of configuration, whether that is a property file, an environment variable, or a hard coded literal. The class itself does not need to know or care where the value originates.
Context: The Unsatisfied Dependency Problem
Before diving into @Value, recall a problem from the dependency injection chapter. When an interface has multiple implementations, Spring does not know which one to inject:
java
public interface Order {
void createOrder();
}
@Component
public class OnlineOrder implements Order {
@Override
public void createOrder() {
System.out.println("Online order created");
}
}
@Component
public class OfflineOrder implements Order {
@Override
public void createOrder() {
System.out.println("Offline order created");
}
}
@RestController
public class UserController {
@Autowired
private Order order; // Spring cannot decide: OnlineOrder or OfflineOrder?
}The application fails with NoUniqueBeanDefinitionException.
Solving It with @Qualifier (Review)
java
@Component
@Qualifier("onlineOrderBean")
public class OnlineOrder implements Order { ... }
@Component
@Qualifier("offlineOrderBean")
public class OfflineOrder implements Order { ... }
@RestController
public class UserController {
@Autowired
@Qualifier("onlineOrderBean") // hardcoded — breaks Dependency Inversion
private Order order;
}This works, but the qualifier name is hardcoded. You have lost the dynamic nature that interfaces are supposed to provide.
Why @Qualifier Alone Breaks Dependency Inversion
Many developers pointed out a valid concern: when you write @Qualifier("onlineOrderBean"), you have hardcoded which implementation to use at compile time. You cannot change it at runtime without recompiling code. This violates the Dependency Inversion Principle, which aims for dynamic substitutability.
There are two better solutions:
- Inject both implementations and choose dynamically at runtime (using
@Qualifier+ business logic) - Use
@Valueto drive which@Beangets created (configuration driven bean creation)
Solution 1: Dynamic Selection Using Both Qualifiers
Inject both implementations and choose which to use based on business logic:
java
@RestController
public class UserController {
@Autowired
@Qualifier("onlineOrderBean")
private Order onlineOrder;
@Autowired
@Qualifier("offlineOrderBean")
private Order offlineOrder;
@PostMapping("/createOrder")
public String createOrder(@RequestParam boolean isOnline) {
// Dynamic selection at runtime — no hardcoding of which one to use
Order selected = isOnline ? onlineOrder : offlineOrder;
selected.createOrder();
return "Order created";
}
}Both beans exist in the container. The selection happens at runtime based on the request parameter. This is the industry standard approach and does not violate Dependency Inversion because the caller decides which implementation to use, not the class itself.
Solution 2: Configuration Driven Bean Creation with @Value
What Is @Value?
@Value is a Spring annotation used to inject values from external sources into a field, constructor parameter, or method parameter. Sources include:
application.propertiesorapplication.ymlfiles- Environment variables
- System properties
- Inline literal values
Syntax
java
@Value("${property.key}") // from application.properties
@Value("${property.key:default}") // with a fallback default
@Value("#{expression}") // Spring Expression Language (SpEL)
@Value("literal value") // inline literalBasic Example
In application.properties:
properties
app.greeting=Hello, World!
app.maxRetries=3
app.featureEnabled=trueIn your Java class:
java
@Component
public class AppConfig {
@Value("${app.greeting}")
private String greeting;
@Value("${app.maxRetries}")
private int maxRetries;
@Value("${app.featureEnabled}")
private boolean featureEnabled;
// Spring injects these from application.properties
}Spring reads the property file at startup and populates each field through reflection before the @PostConstruct method is called.
Using @Value for Dynamic Bean Creation
Now let us apply @Value to solve the Order interface problem. Instead of using @Component on the implementations, we use @Configuration with @Bean and let a property value determine which implementation is returned:
Remove @Component from the implementations:
java
// No @Component here — we control creation manually
public class OnlineOrder implements Order {
@Override
public void createOrder() {
System.out.println("Online order created");
}
}
public class OfflineOrder implements Order {
@Override
public void createOrder() {
System.out.println("Offline order created");
}
}Create a @Configuration class that uses @Value:
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OrderConfig {
@Value("${order.isOnline}") // inject from application.properties
private boolean isOnline;
@Bean
public Order createOrderBean() {
if (isOnline) {
return new OnlineOrder();
} else {
return new OfflineOrder();
}
}
}In application.properties:
properties
order.isOnline=trueResult: Spring runs createOrderBean(), sees isOnline = true, and registers an OnlineOrder as the Order bean. To switch to offline, you change application.properties — no code change needed. This is configuration driven bean creation.
Complete Working Example
java
// Order interface
public interface Order {
void createOrder();
}
// Implementations — no Spring annotations
public class OnlineOrder implements Order {
@Override
public void createOrder() {
System.out.println("[OnlineOrder] Creating online order");
}
}
public class OfflineOrder implements Order {
@Override
public void createOrder() {
System.out.println("[OfflineOrder] Creating offline order");
}
}
// Configuration class — decides which implementation to register
@Configuration
public class OrderConfig {
@Value("${order.isOnline:true}") // defaults to true if not set
private boolean isOnline;
@Bean
public Order order() {
System.out.println("isOnline = " + isOnline);
return isOnline ? new OnlineOrder() : new OfflineOrder();
}
}
// Controller — depends on the Order abstraction
@RestController
public class UserController {
private final Order order;
@Autowired
public UserController(Order order) {
this.order = order;
}
@PostMapping("/createOrder")
public String createOrder() {
order.createOrder();
return "Done";
}
}With order.isOnline=true in application.properties:
isOnline = true
[OnlineOrder] Creating online orderAfter changing to order.isOnline=false (restart required):
isOnline = false
[OfflineOrder] Creating offline orderInjecting Different Types of Values
java
@Component
public class AppSettings {
// String value
@Value("${app.name}")
private String appName;
// Integer value
@Value("${app.timeout:30}") // default 30 if not present
private int timeoutSeconds;
// Boolean value
@Value("${app.debug:false}")
private boolean debugMode;
// List of values from comma separated property
// app.allowedOrigins=http://localhost:3000,https://myapp.com
@Value("${app.allowedOrigins}")
private List<String> allowedOrigins;
// Inline literal — no property file needed
@Value("hardcodedValue")
private String constant;
// Spring Expression Language (SpEL)
@Value("#{2 * 60 * 1000}") // evaluates to 120000
private long timeoutMillis;
// SpEL with system property
@Value("#{systemProperties['user.home']}")
private String userHome;
@PostConstruct
public void print() {
System.out.println("App: " + appName);
System.out.println("Timeout: " + timeoutSeconds + "s");
System.out.println("Debug: " + debugMode);
System.out.println("Origins: " + allowedOrigins);
System.out.println("Timeout ms: " + timeoutMillis);
}
}Using @Value in Constructor Injection
@Value works with constructor injection too, which is the recommended style:
java
@Component
public class DatabaseClient {
private final String host;
private final int port;
private final String schema;
public DatabaseClient(
@Value("${db.host:localhost}") String host,
@Value("${db.port:5432}") int port,
@Value("${db.schema:public}") String schema
) {
this.host = host;
this.port = port;
this.schema = schema;
}
@PostConstruct
public void connect() {
System.out.println("Connecting to " + host + ":" + port + "/" + schema);
}
}The corresponding application.properties:
properties
db.host=prod-db.example.com
db.port=5432
db.schema=ordersDefault Values
You can provide a default that is used when the property is missing from the configuration file:
java
@Value("${feature.newUI:false}") // false if feature.newUI not defined
private boolean useNewUI;
@Value("${service.url:http://localhost:8080}")
private String serviceUrl;This prevents the application from failing to start when optional configuration is absent.
Common Pitfalls
Pitfall 1: @Value on a static field
java
@Value("${app.name}")
private static String appName; // DOES NOT WORK — Spring ignores static fieldsSpring IoC manages instances, not static class members. Use an instance field instead.
Pitfall 2: Missing property with no default
java
@Value("${app.secret}")
private String secret; // If app.secret is not in properties, app fails to startAlways provide a default for optional properties: @Value("${app.secret:}") defaults to empty string.
Pitfall 3: @Value in a class not managed by Spring
java
// Not a Spring bean — @Value is ignored
public class UtilHelper {
@Value("${app.name}")
private String name; // null at runtime
}@Value only works inside classes that Spring manages (annotated with @Component, @Service, @Configuration, etc.).
Summary
| Feature | Description |
|---|---|
@Value("${key}") | Inject from application.properties |
@Value("${key:default}") | Inject with a fallback default |
@Value("literal") | Inject a hardcoded literal |
@Value("#{expression}") | Evaluate a Spring Expression Language expression |
| Works in | Fields, constructor parameters, setter parameters |
| Does not work in | Static fields, classes not managed by Spring |
@Value is most useful for injecting individual configuration values into a bean. When you have a large group of related properties, @ConfigurationProperties (covered in the next chapter) is a better fit.
Interview Questions
Q1. What is the @Value annotation in Spring Boot?@Value is used to inject values from external configuration sources (like application.properties), environment variables, system properties, or inline literals directly into Spring managed bean fields or constructor parameters.
Q2. What sources can @Value read from? It can read from application.properties / application.yml, environment variables, system properties, inline literal strings, and Spring Expression Language (SpEL) expressions.
Q3. How do you provide a default value with @Value? Use the colon syntax: @Value("${property.key:defaultValue}"). If the property is absent, the default is used instead of throwing an error.
Q4. Can you use @Value on a static field? No. Spring manages bean instances, not static class state. @Value on a static field is silently ignored. Always use instance fields.
Q5. How does @Value help achieve dynamic bean initialisation? You can use @Value inside a @Configuration class to read a property from application.properties, and then use that value inside a @Bean method to decide which implementation to instantiate and register. Changing the property and restarting the application changes which bean is active, without modifying any Java code.
Q6. What is the difference between @Value and @ConfigurationProperties?@Value injects one property at a time and is best for simple, individual values. @ConfigurationProperties maps a group of related properties to a typed Java object, supporting nested structures, lists, maps, and validation annotations. For complex or growing configuration, @ConfigurationProperties is cleaner and more maintainable.
Q7. Does @Value work in a class that is not a Spring bean? No. @Value is processed by Spring's IoC container at bean creation time. If the class is not managed by Spring (no @Component, @Service, @Configuration, etc.), Spring never processes its annotations and the field remains null.
Q8. Can @Value inject a List? Yes. If your property contains comma separated values (e.g., app.tags=java,spring,boot), you can inject it as a List<String> using @Value("${app.tags}") on a List<String> field. Spring automatically splits the value.