@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.

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:

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:

3. static

@Value does not work with static fields.

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.

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:

And our class has:

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:

Java class:

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

3. Environment Variables

If our environment has:

Then in Java:

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

4. System Properties (-Dproperty=value)

Run our application like this:

Then in code:

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:

Configuration class:

Then:

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:

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.

2. Constructor Parameter Injection

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

3. Setter Method Injection

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

4. Method Parameter Injection (@Bean method)

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

In this case, AppService might look like this:

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.

2. Mathematical Operations

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

3. Conditional (Ternary) Expressions

Inject values conditionally at runtime.

4. System Properties and Environment Variables

We can access system properties or environment variables dynamically.

5. Method Calls

SpEL allows calling static methods.

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.

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.

Or even:

8. Null-safe Navigation and Defaulting

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

Examples

List of Values

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:

Using @Value:

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:

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:

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:

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