Overview

Binding Configuration to JavaBeans in Spring Boot

2 Comments

It is quite easy to assign external configuration values to variables in Spring. The @Value annotation has been available in the Spring Framework for a long time now. With the introduction of @ConfigurationProperties in Spring Boot, a new way for binding configuration values to JavaBeans has been introduced. Depending on how configuration data is passed to an application, there are slight differences in how configuration is bound to JavaBeans. I have been very confused about this recently, so I’m going explain said differences in this blog post.

This blog post in build on false assumptions. Please read How I Caused Confusion about Spring Boot before you continue.

Reading Configuration from different sources

There are many ways to specify configuration data in Spring Boot and the framework defines a order for overriding configuration values from different sources. This way we can, for example, use profiles to define different environments for an application (like local, dev, qa and prod). Another option is to put local configuration into the application.properties file and provide configuration for the different environments via environment variables. Specifying configuration via environment variables is the recommended way for scalable applications according to the 12 factor method. For this reason I’m going to show how environment variables override configuration data from the application.properties file.
In the remainder of this blog post, I’m going to use a small example project to analyse the binding of configuration data. I’m assuming that you’re familiar with Spring Boot in general and the @Value and @ConfigurationProperties annotations.

The spring-boot-configuration-example project

There are differences in the way Spring binds configuration from environment variables and from property files to JavaBeans. Furthermore the result of the binding depends on whether you use the @Value annotation or @ConfigurationProperties. To demonstrate this differences, I have created the example project codecentric/spring-boot-configuration-example. The project allows you to try this for yourself and to play around with different settings.

The project provides two configuration classes for reading configuration data: AnnotationConfiguration, which uses the @Value annotation and TypeSafeConfiguration, which uses the @ConfigurationProperties annotation. AnnotationConfiguration is defined with the following Spring Expression Language (SpEL) expressions to read configuration data:

@Component
public class AnnotationConfiguration {
    @Value("${example.property-name:not set}")
    public String propertyDashName;
 
    @Value("${example.property.name:not set}")
    public String propertyPointName;
 
    @Value("${example.propertyName:not set}")
    public String propertyName;
 
    @Value("${example.propertyname:not set}")
    public String propertyname;
 
    @Value("${example.property_name:not set}")
    public String property_name;
}

TypeSafeConfiguration on the other hand uses the configuration prefix “example” and has the following fields:

@Component
@ConfigurationProperties(prefix = "example")
public class TypeSafeConfiguration {
    private String propertyName;
 
    private String propertyname;
 
    private String property_name;
}

Note that we can not implement an equivalent for example.property-name and example.property.name in TypeSafeConfiguration, since neither is a valid field name in Java. If you want to inspect how Spring binds configuration values to this classes all you need to do is execute the run.sh script (hope you’re using Unix/Linux). It will print out what it is doing and how the configuration affects the state of the application. The following steps are executed in order:

  1. Run the application without any configuration.
  2. Run the application with environment variables set.
  3. Run the application with application.properties.
  4. Run the application with both, environment variables and application.properties.

When running with environment variables (Step 2 and 4), the script will set the following environment variables:

export EXAMPLE_PROPERTY_NAME=EXAMPLE_PROPERTY_NAME
export EXAMPLE_PROPERTYNAME=EXAMPLE_PROPERTYNAME

Note the missing underscore in the second case. It will be interesting to so, how Spring maps this to our configuration classes. For example I can not tell which of the SpEL expressions in AnnotationConfiguration will get the value of EXAMPLE_PROPERTYNAME. When the run script executes the application with a properties file, the script will write the following to src/main/resources/application.properties:

example.property.name=example.property.name
example.property-name=example.property-name
example.property_name=example.property_name
example.propertyname=example.propertyname
example.propertyName=example.propertyName

The mapping between properties and SpEL expressions is pretty obvious, since there is a one to one mapping for each SpEL expression. However I have no idea which values will get bound to TypeSafeConfiguration. So let’s run this and have a look at it! You can run it yourself like this:

git clone https://github.com/codecentric/spring-boot-configuration-example
cd spring-boot-configuration-example
./run.sh

In the remainder of this blog post, I’m going through the output of run.sh. If you are interested in the details, I have created this gist containing the output. Not surprisingly, all fields will be unset, when running the application without configuration.

Reading Configuration from environment variables

Running the example with just environment variables set is a bit more interesting. The log shows the following bindings for TypeSafeConfiguration (I’m using colors to make it easier to spot the differences):

Field nameConfiguration value
propertyNameEXAMPLE_PROPERTY_NAME
propertynameEXAMPLE_PROPERTYNAME
property_namenull

… and for the SpEL expressions in AnnotationConfiguration:

SpEL expressionConfiguration value
example.property-namenot set
example.property.nameEXAMPLE_PROPERTY_NAME
example.propertyNameEXAMPLE_PROPERTYNAME
example.propertynameEXAMPLE_PROPERTYNAME
example.property_nameEXAMPLE_PROPERTY_NAME

Looking at the first table we can see that the TypeSafeConfiguration.propertyName is set to the value of EXAMPLE_PROPERTY_NAME, while TypeSafeConfiguration.propertyname is set to the value of EXAMPLE_PROPERTY_NAME. TypeSafeConfiguration.property_name can not be set by environment variables. The results for AnnotationConfiguration can be seen in the second table: example.property.name and example.property_name both get the value of EXAMPLE_PROPERTY_NAME, while example.propertyName and example.propertyname get the value of EXAMPLE_PROPERTYNAME. The SpEL expression example.property-name can not be set by environment variables.

What is interesting about this? Looking only at the results for TypeSafeConfiguration, I would expect that the environment variable EXAMPLE_PROPERTY_NAME gets mapped to TypeSafeConfiguration.property_name. But instead the value of EXAMPLE_PROPERTYNAME is used. This feels especially confusing to me, when comparing it with the second table. Here the SpEL expression example.property_name will get the value of EXAMPLE_PROPERTY_NAME!

Another inconsistency is the handling of TypeSafeConfiguration.propertyName and TypeSafeConfiguration.propertyname compared to the handling of the SpEL expressions example.propertyName and example.propertyname. TypeSafeConfiguration.propertyName gets the value of EXAMPLE_PROPERTY_NAME while TypeSafeConfiguration.propertyname gets the value of EXAMPLE_PROPERTYNAME, but both SpEL expressions get the value of EXAMPLE_PROPERTYNAME.

The last observation we can make is, that it is not possible to set SpEL expressions containing dashes via environment variables. This is particulary cumbersome since the recommended way for specifying keys in property files, according to the Spring Boot documentation, is to use dashes (see Table 24.1 in the Spring Boot documentation). Imaging a team building all of it’s configuration based on dash-separated keys in properties files, only to notice that they cannot set these values using environment variables, when deploying to production.

Reading Configuration from a properties file

The next part of the run.log shows how configuration from an application.properties file will be mapped to our configuration beans. Here’s a summary of the output for TypeSafeConfiguration:

Field nameConfiguration value
propertyNameexample.propertyName
propertynameexample.propertyname
property_nameexample.property-name

… and for AnnotationConfiguration:

SpEL expressionConfiguration value
example.property-nameexample.property-name
example.property.nameexample.property.name
example.propertyNameexample.propertyName
example.propertynameexample.propertyname
example.property_nameexample.property_name

The data in AnnotationConfiguration is exactly what we expected, so no need to talk about that. What’s really weird is that TypeSafeConfiguration.property_name it set to the value of example.property-name and not example.property_name. I have no idea why it behaves this way. Furthermore we can see, that it is possible to set all values, which was not possible when using only environment variables.

Mixing configuration from environment variables with configuration from properties files

The last thing to have a look at, is how configuration is overridden when providing both, application.properties and envrionment variables. Again here is the result for TypeSafeConfiguration:

Field nameConfiguration value
propertyNameEXAMPLE_PROPERTY_NAME
propertynameEXAMPLE_PROPERTYNAME
property_nameexample.property-name

… and for AnnotationConfiguration:

SpEL expressionConfiguration value
example.property-nameexample.property-name
example.property.nameEXAMPLE_PROPERTY_NAME
example.propertyNameEXAMPLE_PROPERTYNAME
example.propertynameEXAMPLE_PROPERTYNAME
example.property_nameEXAMPLE_PROPERTY_NAME

Since environment variables have precedence over configuration from application.properties, everything that can be initialized from environment variables will be. Only TypeSafeConfiguration.property_name and the SpEL expression example.property-name will be set to the respective value from application.properties.

Conclusion

There two ways to bind configuration data to JavaBean in Spring Boot: by using type safe binding, via @ConfigurationProperties and by using SpEL expressions via @Value. Convenient as it may be, the results can be confusing depending on whether configuration is set from environment variables or properties files. As a take away, my recommendations are:

  • Be consistent with: don’t mix camel case, snake case, and the like when defining properties.
  • Don’t use dashes in property keys.
  • Don’t use underscores in field names.

This will save you a lot of headaches when working with configuration values in Spring Boot.

Benedikt Ritter works as a Software Craftsman at codecentric AG in Solingen since September 2013. His joy for creating reliable software is not limited to coding at work: Benedikt is member of the Apache Software Foundation and Committer for the Apache Commons project.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentare

Comment

Your email address will not be published. Required fields are marked *