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;
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
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.