DEV Community

Cover image for Type-safe Configuration Properties in Spring Boot 2
scottshipp
scottshipp

Posted on • Updated on • Originally published at code.scottshipp.com

Type-safe Configuration Properties in Spring Boot 2

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"
}
Enter fullscreen mode Exit fullscreen mode

The response received from such a POST is:

{
  "orderNumber": 1,
  "customerOrder": {
    "product": "latte",
    "size": "tall"
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }

}
Enter fullscreen mode Exit fullscreen mode

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(", "));
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (0)