@Value for Property Injection

About

In Spring Framework, configuration and dependency management are central to how applications are designed. The @Value annotation plays a vital role in this ecosystem by allowing values from external sources—such as property files, environment variables, or system properties—to be injected directly into beans. This supports the development of configurable and environment-agnostic applications.

The @Value annotation is supported by Spring’s core container and relies on the use of a property placeholder syntax (${...}) or Spring Expression Language (SpEL) expressions (#{...}).

Purpose

The primary purpose of @Value is to enable injection of literal values and externalized configuration into beans. This helps in achieving separation of concerns by externalizing environment-specific settings from code logic.

It enables:

  • Reading configuration from application.properties or application.yml.

  • Providing default values for missing configuration.

  • Evaluating simple or complex expressions at runtime using SpEL.

  • Referencing system or environment variables seamlessly.

This annotation bridges the gap between static configuration and dynamic runtime environments by supporting property resolution directly in the code.

Access Modifiers with @Value

1. public,private, protected, default (package-private)

All of these work with @Value. Spring will inject the value regardless of visibility.

@Value("${app.name}")
public String appName1;

@Value("${app.version}")
protected String appVersion;

@Value("${app.author}")
String author; // package-private

Best practice: Use private unless we have a valid reason otherwise. Exposing configuration fields publicly is rarely needed and can lead to poor encapsulation.

2. final

Spring cannot inject values into final fields via field injection.

This will fail at runtime:

@Value("${app.name}")
private final String appName; // ❌ Not allowed

Why? Spring injects values via reflection after the object is created. Final fields must be initialized during construction, and cannot be modified afterward.

Alternative: Use constructor injection for final fields:

public class AppConfig {

    private final String appName;

    public AppConfig(@Value("${app.name}") String appName) {
        this.appName = appName;
    }
}

3. static

@Value does not work with static fields.

@Value("${app.name}")
private static String appName; // ❌ Will not work

Why? Spring manages beans as instances, not static contexts. It does not inject values into static members because static fields belong to the class, not to a bean instance.

Workaround: We can read the value in a non-static field or use @PostConstruct to assign it to a static variable, though this is considered a workaround and not idiomatic.

@Component
public class AppConfig {
    private static String staticName;

    @Value("${app.name}")
    private String appName;

    @PostConstruct
    public void init() {
        staticName = appName;
    }

    public static String getAppName() {
        return staticName;
    }
}

Property Resolution and Precedence

When resolving the value of a property referenced using @Value, Spring searches in a defined order of precedence. This includes:

  1. Command-line arguments

  2. application.properties or application.yml in the src/main/resources directory

  3. Environment variables

  4. System properties (e.g., -Dproperty=value)

  5. @PropertySource annotated configuration classes

  6. Default values defined in the annotation itself

This resolution mechanism ensures flexibility and allows the application to be configured externally without recompilation.

1. Command-line Arguments

If we pass a property when starting the Spring Boot application:

java -jar app.jar --app.name=FromCommandLine

And our class has:

@Value("${app.name}")
private String appName;

Result: appName = "FromCommandLine" Even if app.name is defined in application.properties, the command-line value takes precedence.

2. application.properties or application.yml (in src/main/resources)

application.properties:

app.name=FromPropertiesFile

Java class:

@Value("${app.name}")
private String appName;

Result: appName = "FromPropertiesFile" This is the most common source of external configuration.

3. Environment Variables

If our environment has:

export APP_NAME=FromEnv

Then in Java:

@Value("${APP_NAME}")
private String appName;

Result: appName = "FromEnv" Spring can resolve values directly from environment variables using uppercase keys.

4. System Properties (-Dproperty=value)

Run our application like this:

java -Dapp.name=FromSystemProperty -jar app.jar

Then in code:

@Value("${app.name}")
private String appName;

Result: appName = "FromSystemProperty" System properties provided via -D override values from the property file if not overridden by command-line args.

5. @PropertySource Annotated Configuration Class

Suppose we have a custom .properties file in our resources folder:

custom-config.properties:

app.name=FromCustomPropertyFile

Configuration class:

@Configuration
@PropertySource("classpath:custom-config.properties")
public class AppConfig {
}

Then:

@Value("${app.name}")
private String appName;

Result: appName = "FromCustomPropertyFile" This allows loading custom property files explicitly, which may override default application properties (if ordered properly).

6. Default Value in the Annotation

If no property is found from any external source:

@Value("${app.name:DefaultApp}")
private String appName;

Result: appName = "DefaultApp" This ensures a fallback is available if no other value is resolved, preventing exceptions on startup.

Supported Injection Targets

The @Value annotation can be applied to the following elements:

  • Fields: The most common usage. Injects the value directly into the member variable.

  • Constructor Parameters: Supports constructor-based dependency injection.

  • Method Parameters: Can inject values into methods (including @Bean methods).

  • Setter Methods: Allows injection during bean initialization through setters.

Each of these allows the developer to choose the injection method best suited to their design pattern, whether it is field-based, constructor-based, or method-based injection.

1. Field Injection

Injects the value directly into a member variable. This is the most common and concise usage.

@Component
public class AppService {

    @Value("${app.name}")
    private String appName;

    public void printName() {
        System.out.println("App Name: " + appName);
    }
}

2. Constructor Parameter Injection

Injects the value via constructor arguments. This is preferred in Spring for better testability and immutability.

@Component
public class AppService {

    private final String appName;

    public AppService(@Value("${app.name}") String appName) {
        this.appName = appName;
    }

    public void printName() {
        System.out.println("App Name: " + appName);
    }
}

3. Setter Method Injection

Injects the value via a setter method. Useful when dependencies or values need to be set after object construction.

@Component
public class AppService {

    private String appName;

    @Value("${app.name}")
    public void setAppName(String appName) {
        this.appName = appName;
    }

    public void printName() {
        System.out.println("App Name: " + appName);
    }
}

4. Method Parameter Injection (@Bean method)

Injects the value into a method parameter, often used in @Configuration classes to define beans.

@Configuration
public class AppConfig {

    @Bean
    public AppService appService(@Value("${app.name}") String appName) {
        return new AppService(appName);
    }
}

In this case, AppService might look like this:

public class AppService {
    private final String appName;

    public AppService(String appName) {
        this.appName = appName;
    }

    public void printName() {
        System.out.println("App Name: " + appName);
    }
}

Special Expressions in @Value

Spring’s @Value annotation supports more than just plain property placeholders. It can evaluate Spring Expression Language (SpEL) expressions enclosed in #{...} syntax. This allows developers to inject computed, conditional, or system-derived values directly into beans during initialization.

The ability to use SpEL with @Value significantly increases its power and flexibility, especially in scenarios that require dynamic or environment-sensitive logic.

Types of Special Expressions

1. Literal Expressions

Inject static values without needing to externalize them.

@Value("#{42}")
private int constantNumber;

@Value("#{'Hello ' + 'World'}")
private String greeting;

2. Mathematical Operations

SpEL supports common arithmetic operations: +, -, *, /, %.

@Value("#{2 * 5}")
private int result;  // 10

@Value("#{20 / 4 + 3}")
private int computedValue;  // 8

3. Conditional (Ternary) Expressions

Inject values conditionally at runtime.

@Value("#{systemProperties['user.country'] == 'US' ? 'Dollar' : 'Other'}")
private String currency;

4. System Properties and Environment Variables

We can access system properties or environment variables dynamically.

@Value("#{systemProperties['user.home']}")
private String userHome;

@Value("#{systemEnvironment['JAVA_HOME']}")
private String javaHome;

5. Method Calls

SpEL allows calling static methods.

@Value("#{T(java.lang.Math).random() * 100}")
private double randomNumber;

@Value("#{T(java.time.LocalDate).now().toString()}")
private String currentDate;

Here, T() is used to refer to a type. We can call any public static method on that type.

6. Accessing Bean Properties

Inject values based on other bean properties using SpEL.

@Value("#{anotherBean.someProperty}")
private String copiedValue;

This enables bean-to-bean data flow at configuration time.

7. Collections and Arrays

SpEL can be used to extract or manipulate elements from collections.

@Value="#{{'Spring', 'Boot', 'Config'}[0]}"
private String firstWord;  // 'Spring'

Or even:

@Value("#{myBean.list[2]}")
private String thirdItemFromList;

8. Null-safe Navigation and Defaulting

Use Elvis (?:) or safe-navigation (?.) operators.

@Value("#{myBean.optionalValue ?: 'default'}")
private String fallback;

@Value("#{myBean?.name}")
private String safeAccess;

Limitations of @Value

While @Value is a convenient way to inject values into Spring-managed beans, it comes with several limitations that make it unsuitable for complex or large-scale configuration scenarios. Understanding these limitations is crucial for choosing the right configuration approach in a Spring application.

1. Not Suitable for Structured or Hierarchical Data

@Value is best suited for injecting simple, individual values like strings, integers, booleans, etc. It does not support binding to nested or grouped properties (e.g., YAML objects or map structures). When dealing with structured data, @ConfigurationProperties is a better fit.

Example:

app:
  user:
    name: Alice
    age: 30

Using @Value:

@Value("${app.user.name}") // Possible
private String name;

@Value("${app.user.age}")  // Possible
private int age;

This becomes unmanageable for larger groups. We cannot bind it into a single class easily.

2. No Type Safety

@Value performs injection at runtime and does not validate the data type until the application starts. If the value in the property file is not compatible with the expected type, it results in a startup failure.

Example:

@Value("${app.maxUsers}")
private int maxUsers;

If app.maxUsers=ten in the properties file, the application will throw a NumberFormatException during startup.

Unlike @ConfigurationProperties, there's no compile-time validation or automatic conversion fallback.

3. No Validation Support

@Value does not support JSR-303 (Bean Validation) annotations like @NotNull, @Min, or @Size. There is no way to enforce or automatically validate constraints on injected values.

If we want validation (e.g., ensuring a property is within a range or not null), we must write custom checks manually in code.

4. Poor IDE and Refactoring Support

Property keys in @Value are written as strings ("${some.property}"), making them prone to typos. Since these are not linked to any strongly-typed construct, refactoring tools in IDEs cannot help update them automatically.

  • No code completion

  • No usage tracking

  • No quick-fix suggestions

This increases the chance of errors, especially in large projects with many configuration entries.

5. Difficult to Test and Maintain

Using @Value for many injected fields scatters configuration logic across multiple classes. It becomes harder to override values for unit tests or mock them in isolation. Over time, it reduces testability and makes configuration behavior harder to reason about.

In contrast, @ConfigurationProperties centralizes all config in one location, which can be easily mocked or injected in test environments.

6. Cannot Inject Complex Collections Easily

While @Value supports simple list or comma-separated value parsing, it is not reliable for complex collections such as lists of objects or maps with nested values.

Example:

app.users=alice,bob,charlie
@Value("#{'${app.users}'.split(',')}")
private List<String> users;

This workaround is fragile and not type-safe. Parsing fails if any edge case arises, and there's no support for structured objects.

7. Not Suitable for Reusability

@Value is not reusable like a configuration bean. If multiple classes need the same property, the same @Value("${...}") line is repeated, leading to duplication and inconsistencies.

With @ConfigurationProperties, we can inject the configuration class wherever needed, promoting DRY principles.

8. Harder to Handle Missing or Optional Properties

If a property is missing, Spring will throw an exception at startup unless we explicitly define a default value using the : syntax.

Example:

@Value("${app.name:DefaultApp}")
private String appName;

If we forget the fallback and the property is missing, the app fails to start. This adds fragility in environments where some properties may be optional or managed externally.

Last updated

Was this helpful?