The @Value Annotation in Spring

Introduction

The main focus of this article is to help you understand how Spring's @Value annotation works.

@Value is a Java annotation that is used at the field or method/constructor parameter level and it indicates a default value for the affected argument. It is commonly used for injecting values into configuration variables - which we will show and explain in the next part of the article.

Basic Assignment

For the easiest examples, we will assign values to three different fields using the @Value annotation by giving them explicit values:

@Value("John")
private String trainee;

@Value("100")
private int hoursOfCode;

@Value("true")
private boolean passedAssesmentTest;

It's very important to note that the argument passed to the @Value annotation can only be a String. Spring will convert the value to the specified type and the assignment will be done without any problems - even if we are passing String values to int or boolean variables.

Spring Environment

Injecting values from properties files with the help of @Value annotation is probably the most used use-case in real-life applications.

We will use the default property file for Spring Boot - application.properties, where we can define variables that we can access afterwards:

car.brand=Audi
car.color=Red
car.power=150
@Value("${car.brand")
private String brand;

@Value("${car.color}")
private String color;

@Value("${car.power}")
private int power;

In this example, the values for the variables are being read from the application.properties file and assigned to them during bean creation.

Most of the time, we'd use this approach to inject configuration values from the application.properties file into beans.

Default Value

Default values are used as a "fallback" if the property we wish to inject isn't defined or missing:

@Value("${car.type:Sedan}")
private String type;

In the above example, because we don't have any car.type property in application.properties, Spring will assign Sedan to the type variable as the default value.

If the car.type property gets inserted into the properties file, the new value will be used instead.

System Variables

We can also access system variables which are stored as properties by the Spring application at start:

@Value("${user.name}")
// Or
@Value("${username}")
private String userName;

@Value("${number.of.processors}")
// Or
@Value("${number_of_processors}")
private int numberOfProcessors;

@Value("${java.home}")
private String java;

The variables can be called with different naming conventions - Spring searches for us and assigns the correct value.

Global Method Value

Because @Value is processed by the BeanPostProcessor class, it will be invoked when Spring is building the Spring context by instantiating configuration files and beans.

This means that by having @Value on a method, all the arguments will be mapped with the value provided to the annotation:

@Value("${car.brand}")
public CarData setCarData(String color, String brand) {
    carData.setCarColor(color);
    carData.setCarBrand(brand);
}

If we are printing carData.getCarColor() and carData.getCarBrand() we will get the car.brand value both times, because like we said, all arguments will be mapped with the value provided.

Parameter Method Value

We can fix this by using @Value directly on the method parameter:

@Value("${car.brand}")
public CarData setCarData(@Value("${car.color}") String color, String brand) {
    carData.setCarColor(color);
    carData.setCarBrand(brand);
}

Now, if we are printing the values from the carData object - the color field will have the car.color value, because we provided the value to the parameter of the method itself.

Spring Expression Language (SpEL)

The Spring Expression Language (SpEL) is an expression language which serves as the foundation for expression evaluation within the Spring portfolio.

Basically, when using SpEL together with the @Value annotation, we are just changing the way we tell Spring what we need. Let's take a closer look:

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

This is how you'd inject a specific system property. On the other hand, we could inject all properties by:

@Value("#{systemProperties}")
private Map<String, String> properties;

We know now, that we can use the @Value annotation for methods as a global value or as a parameter value.

Since constructors are essentially methods, we can use the annotation in constructors as well:

public Driver(@Value("#{systemProperties['user.name']}") String name, String location) {
    this.name = name;
    this.location = location;
}

Injecting into Maps

With SpEL, we can do some other fairly interesting things when coupled with the @Value annotation. For example, let's make a Map that represents indoor and outdoor hobbies of a student. Each of these can have multiple values:

student.hobbies={indoor: 'reading, drawing', outdoor: 'fishing, hiking, bushcraft'}

Now, to inject this, we'll need a Map<String, List<String>>:

@Value("#{${student.hobbies}}")
private Map<String, List<String>> hobbies;

Injecting into Lists

If a property has comma-separated-values, such as a simple list of books, we can use SpEL to interpret it and transform it into a list:

student.booksRead=Harry Potter,The Hobbit,Game of Thrones

By using the split() method, and splitting for every comma (,), we can inject these values into a list:

@Value("#{'${student.booksRead}'.split(',')}")
private List<String> booksRead;

Conclusion

As soon as you end up working on a real application, you realize that configuration is an important topic and if you are using Spring. There's a big chance you already use or you will have to use the @Value annotation extensively.

Understanding this basic functionality is very important because if you don't, you might end up using it totally wrong.

We also need to be aware that not everything that looks simple is also very good for long term solutions. For example, we should only use @Value in encapsulated components/services (we can call them configuration services).

This way, we will have all our configurations in one place, and that component will only have the responsibility of loading and providing them to other components.