Skip to content

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:

  1. Inject both implementations and choose dynamically at runtime (using @Qualifier + business logic)
  2. Use @Value to drive which @Bean gets 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.properties or application.yml files
  • 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 literal

Basic Example

In application.properties:

properties
app.greeting=Hello, World!
app.maxRetries=3
app.featureEnabled=true

In 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=true

Result: 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 order

After changing to order.isOnline=false (restart required):

isOnline = false
[OfflineOrder] Creating offline order

Injecting 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=orders

Default 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 fields

Spring 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 start

Always 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

FeatureDescription
@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 inFields, constructor parameters, setter parameters
Does not work inStatic 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.