Who this tutorial is for
This tutorial is for anyone who has used Spring Boot enough to get their feet wet with the @Value
annotation and retrieving properties from files (like application.properties
), environment variables, and command-line arguments. Or just learn all about that stuff in my Properties in Spring Boot 2 Tutorial first and then come back to this one.
Getting started
For this post, I have a barebones Spring Boot 2 web service set up that can take coffee orders. Clients of my service can POST a new order to /orders
. Each order needs a size and product. If a customer wants a tall latte, the following request body would create the new order:
{
"product": "latte",
"size": "tall"
}
The response received from such a POST is:
{
"orderNumber": 1,
"customerOrder": {
"product": "latte",
"size": "tall"
}
}
In addition, anyone can send a GET request to retrieve an existing order. To get the above order, clients can send a GET to /orders/1
and the above response will be received.
I’ve chosen to use properties to hold the products and sizes that our coffee shop can serve. This is my application.properties
file right now:
menu.sizes=short,tall,grande,vente
menu.products=americano,cappucino,latte,coffee
Although this is not a good use of properties for a production application, it works for illustrative purposes in this specific tutorial.
Using menu properties with @Value
With the @Value
annotation, I can refer to these menu properties in a class somewhere, such as follows:
@Service
public class OrderValidator {
@Value("${menu.sizes}")
private List<String> sizes;
@Value("${menu.products}")
private List<String> products;
public void validate(CustomerOrder order) {
if(!sizes.contains(order.getSize().toLowerCase())) {
throw new BadOrderException("Size is invalid. Allowable sizes are " + sizes.stream().collect(Collectors.joining()));
}
if(!products.contains(order.getProduct().toLowerCase())) {
throw new BadOrderException("Selected product is invalid. Allowable products are " + products.stream().collect(Collectors.joining()));
}
}
}
I could use the OrderValidator to validate orders that come into my controller:
@PostMapping
public StoredOrder newOrder(@RequestBody CustomerOrder order) {
orderValidator.validate(order);
long orderNumber = orderDb.storeOrder(order);
return new StoredOrder(orderNumber, order);
}
Type-safe configuration properties
Spring Boot offers another approach for managing properties known as type-safe configuration properties. You can create a class to hold all related properties instead of adding a @Value
annotation.
@ConstructorBinding
@ConfigurationProperties("menu")
public class MenuProperties {
private final List<String> sizes;
private final List<String> products;
public MenuProperties(List<String> sizes, List<String> products) {
this.sizes = sizes;
this.products = products;
}
public List<String> getProducts() {
return products;
}
public List<String> getSizes() {
return sizes;
}
}
The @ConstructorBinding
annotation tells Spring Boot to use the constructor to inject the properties. You could omit this if you provide setters and remove the final declaration from the class members. It is a recommended practice to use immutable configuration properties classes, though, as shown here.
The @ConfigurationProperties
annotation tells Spring Boot 2 to look in its properties sources for properties with the provided “prefix.” In this case, that simply means that it will look in and find properties with the “menu” prefix in application.properties
.
Note that you can also specify longer prefixes. For example, if you had “menu.items.sizes” and “menu.items.products” as your properties, you could pass “menu.items” to the @ConfigurationProperties
annotation: @ConfigurationProperties("menu.items")
.
In order for @ConfigurationProperties
-annotated classes to be found by Spring Boot and injected with the properties, add the @ConfigurationPropertiesScan
to whatever class in your application is marked with the @SpringBootApplication
annotation. In my example web service, this is CoffeeShopApplication.java
in the root directory of my sources, which now looks like this:
@SpringBootApplication
@ConfigurationPropertiesScan
public class CoffeeshopApplication {
public static void main(String[] args) {
SpringApplication.run(CoffeeshopApplication.class, args);
}
}
As an alternative, you can use the @EnableConfigurationProperties
annotation to specify only specific classes.
Consuming configuration properties
To consume the new properties class, just add a constructor to the OrderValidator
that accepts a parameter of the new type. Spring will autowire the dependency:
@Service
public class OrderValidator {
private final MenuProperties menuProperties;
public OrderValidator(MenuProperties menuProperties) {
this.menuProperties = menuProperties;
}
public void validate(CustomerOrder order) {
if(!menuProperties.getSizes().contains(order.getSize().toLowerCase())) {
throw new BadOrderException("Size is invalid. Allowable sizes are " + getAllowableSizes());
}
if(!menuProperties.getProducts().contains(order.getProduct().toLowerCase())) {
throw new BadOrderException("Selected product is invalid. Allowable products are " + getAllowableProducts());
}
}
private String getAllowableProducts() {
return menuProperties.getProducts().stream().collect(Collectors.joining(", "));
}
private String getAllowableSizes() {
return menuProperties.getSizes().stream().collect(Collectors.joining(", "));
}
}
Advantages
There are a number of advantages to this approach. Some of them are evident from this example, and some of them aren’t. The major ones are:
It makes properties available in reusable classes
It is much less cumbersome to use for larger groups of properties or for hierarchical properties
There is a feature of Spring Boot 2 that allows your IDE to autocomplete properties for you when they are exposed as type-safe configuration properties
There is a validation feature available for properties made available as type-safe configuration properties
Because of the advantages of type-safe configuration properties, it is a recommended practice to prefer them to the @Value
annotation approach.
What you have learned
In this post, you learned how to create type-safe configuration properties in Spring Boot 2.
If you want to learn more about Spring Boot 2, please check out the Spring Guides.
Top comments (0)