Spring Boot Configuration Properties Explained
Do you get lost in the configuration annotations of Spring Boot? Take a look at the configuration annotations, what they mean, and how you can apply them in your code.
Join the DZone community and get the full member experience.
Join For FreeDo you also get lost in the configuration annotations of Spring Boot and how to use them? In this blog, you will take a look at the configuration annotations, what they mean, and how you can apply them in your code — everything is explained by means of examples. Enjoy!
Introduction
The annotations containing the word configuration in Spring Boot can be overwhelming. You have @Configuration
, @EnableConfigurationProperties
, @ConfigurationPropertiesScan
, etc. But what do they actually do, and how do you need to apply them in your code?
A good starting point for applying best practices can be found in the official Spring Boot documentation (search for configuration). However, it lacks clear examples, in my opinion. That is the goal of this blog: explain the different options, explain the differences, and show how they can be used by means of examples.
The source code used in this blog can be found on GitHub.
Sample Application
The sample application is created with start.spring.io and makes use of the Spring Web and Lombok dependencies.
This section gives an overview of how the repository and the basic application are set up. Details will be made clear in the sections hereafter.
The properties are located in the MyProperties
class and consist out of:
- a boolean
enabled
; - a string
stringConfig
; - a nested
additional
configuration item consisting of:- a boolean
addEnabled
; - a string
addString
.
- a boolean
The boolean values contain their default value, and the string values will be given a value in the application.properties
file. The value of the addString
property will also contain a reference to the stringConfig
property. This way, you can combine property values.
my.properties.string-config=First piece
my.properties.additional.add-string=${my.properties.string-config} Second piece
One ConfigurationController
is added, which reads the configuration properties and returns them.
@RequestMapping("/configuration")
public String configuration() {
StringBuilder result = new StringBuilder();
result.append("Value of enabled = ").append(myProperties.isEnabled()).append("\n");
result.append("Value of stringConfig = ").append(myProperties.getStringConfig()).append("\n");
if (myProperties.getAdditional() != null) {
result.append("Value of additional.addEnabled = ").append(myProperties.getAdditional().isAddEnabled()).append("\n");
result.append("Value of additional.addString = ").append(myProperties.getAdditional().getAddString()).append("\n");
}
return result.toString();
}
The application consists of several modules. Each module corresponds to a different topic covered in this blog.
Build the application:
$ mvn clean verify
Navigate to the directory of a module and run the application:
$ mvn spring-boot:run
The endpoint can be invoked as follows:
$ curl http://localhost:8080/configuration
Each module contains three tests:
HttpRequestIT
: This test will start a complete server using the@SpringBootTest
annotation;WebLayerIT
: This test starts an application context with a limited number of beans using the@WebMvcTest
annotation;WebLayerTestPropertiesIT
: identical toWebLayerIT
, but this time, specific application properties are used.
@ConfigurationProperties + @Component
In module config1, you will map the properties to config class MyProperties
by means of the @ConfigurationProperties
annotation. In order to use it in the controller, you also add the @Component
annotation. Also note that setters and getters need to be available in the MyProperties
class, these are generated by means of the @Getter
and @Setter
Lombok annotations.
The MyProperties
class is the following:
@Getter
@Setter
@Component
@ConfigurationProperties("my.properties")
public class MyProperties {
private boolean enabled;
private String stringConfig;
private final Additional additional = new Additional();
@Getter
@Setter
public static class Additional {
private boolean addEnabled;
private String addString;
}
}
In the controller, you just need to inject the MyProperties
component.
@RestController
public class ConfigurationController {
private final MyProperties myProperties;
@Autowired
public ConfigurationController(MyProperties myProperties) {
this.myProperties = myProperties;
}
...
}
The tests do not need any specific annotations in this case.
Full Spring Server Test
This test (HttpRequestIT
) is annotated with @SpringBootTest
and it is ensured that a random port is being used in order to prevent port conflicts when running the test. The endpoint is called, and the response is validated. No additional annotations are needed in order to use the properties.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HttpRequestIT {
@Value(value="${local.server.port}")
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
public void configurationShouldReturnProperties() throws Exception {
assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/configuration",
String.class)).contains("""
Value of enabled = false
Value of stringConfig = First piece
Value of additional.addEnabled = false
Value of additional.addString = First piece Second piece""");
}
}
Spring Application Context Without Server Test
This test (WebLayerIT
) is annotated with @WebMvcTest
meaning that this test starts an application context with a limited number of beans. Therefore, you need to add the @EnableConfigurationProperties
annotation in order to use the properties. @EnableConfigurationProperties
creates a binding between a configuration class and the test.
@WebMvcTest
@EnableConfigurationProperties(MyProperties.class)
public class WebLayerIT {
@Autowired
private MockMvc mockMvc;
@Test
public void configurationShouldReturnProperties() throws Exception {
this.mockMvc.perform(get("/configuration")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("""
Value of enabled = false
Value of stringConfig = First piece
Value of additional.addEnabled = false
Value of additional.addString = First piece Second piece""")));
}
}
Test Application Properties
The above tests use the application properties as defined in your application. For test purposes, it is often needed to use test-specific application properties. These can be put in the tests directory. In order to use them, you have to use the @TestPropertyResource
annotation. The test WebLayerTestPropertiesIT
is identical to the above WebLayerIT
, besides that, it uses the test application properties.
@WebMvcTest
@EnableConfigurationProperties(MyProperties.class)
@TestPropertySource(locations = "/application-test.properties")
public class WebLayerTestPropertiesIT {
@Autowired
private MockMvc mockMvc;
@Test
public void configurationShouldReturnProperties() throws Exception {
this.mockMvc.perform(get("/configuration")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("""
Value of enabled = false
Value of stringConfig = First test piece
Value of additional.addEnabled = false
Value of additional.addString = First test piece Second piece""")));
}
}
@ConfigurationProperties + @EnableConfigurationProperties
In module config2, you will use again the @ConfigurationProperties
, just like in module config1, but this time without the @Component
annotation. In order to use the properties in the controller, you need to add the @EnableConfigurationProperties
annotation.
The MyProperties
class without the @Component
annotation:
@Getter
@Setter
@ConfigurationProperties("my.properties")
public class MyProperties {
...
}
The controller with the @EnableConfigurationProperties
annotation:
@RestController
@EnableConfigurationProperties(MyProperties.class)
public class ConfigurationController {
private final MyProperties myProperties;
@Autowired
public ConfigurationController(MyProperties myProperties) {
this.myProperties = myProperties;
}
...
}
It is not necessary to add the @EnableConfigurationProperties
annotation in the tests because this annotation is already added to the controller.
@ConfigurationProperties + @Component
In module config3, you will use the same setup as in module config1, but this time with the @Configuration
annotation. The @Configuration
annotation creates a Spring bean of configuration stereotype. The @EnableConfigurationProperties
annotation should not be used directly together with the @Configuration
annotation, see also this Stack Overflow answer of Andy Wilkinson.
The MyProperties
class is identical to the one of module config2:
@Getter
@Setter
@ConfigurationProperties("my.properties")
public class MyProperties {
...
}
You introduce a new class ApplicationConfig
that will act as a configuration bean. You annotate it, therefore with @Configuration
. You also need to annotate it with @EnableConfigurationProperties
so that the properties are available to the bean.
@Configuration
@EnableConfigurationProperties(MyProperties.class)
public class ApplicatonConfig {
}
The controller does not need any extra annotations:
@RestController
public class ConfigurationController {
private final MyProperties myProperties;
@Autowired
public ConfigurationController(MyProperties myProperties) {
this.myProperties = myProperties;
}
...
}
The tests are identical to the tests of config1, it is necessary to add the @EnableConfigurationProperties
annotation to the @WebMvcTest
tests.
@ConfigurationProperties + @ConfigurationPropertiesScan
In module config4, you use @ConfigurationProperties
to map the properties to class MyProperties
. This time, you add annotation @ConfigurationPropertiesScan
to the class MySpringBootConfigurationPlanetApplication
, the one annotated with @SpringBootApplication
. With the @ConfigurationPropertiesScan
annotation, you can also specify which packages contain the configuration classes.
The main class is defined as follows:
@SpringBootApplication
@ConfigurationPropertiesScan("com.mydeveloperplanet.myspringbootconfigurationplanet.config4.config")
public class MySpringBootConfigurationPlanetApplication {
public static void main(String[] args) {
SpringApplication.run(MySpringBootConfigurationPlanetApplication.class, args);
}
}
The MyProperties
class only needs @ConfigurationProperties
.
@Getter
@Setter
@ConfigurationProperties("my.properties")
public class MyProperties {
...
}
The controller does not need any configuration annotations:
@RestController
public class ConfigurationController {
private final MyProperties myProperties;
@Autowired
public ConfigurationController(MyProperties myProperties) {
this.myProperties = myProperties;
}
...
}
The tests are again identical to the tests of config1.
Constructor Binding
In module config5, you use the same setup as the previous config4 module. This time, you will use constructor binding in the MyProperties
class. The differences are:
- No need to specify setters, and the properties can be made final;
- You need to add a constructor to map the configuration;
- Default values need to be specified by means of the
@DefaultValue
annotation in the constructor argument list; - You may need to annotate nested properties with
@DefaultValue
, otherwise, they are considered to be absent when no property of the nested property is assigned a value.
The MyProperties
class is the following:
@Getter
@ConfigurationProperties("my.properties")
public class MyProperties {
private final boolean enabled;
private final String stringConfig;
private final Additional additional;
public MyProperties(boolean enabled, String stringConfig, @DefaultValue Additional additional) {
this.enabled = enabled;
this.stringConfig = stringConfig;
this.additional = additional;
}
@Getter
public static class Additional {
private final boolean addEnabled;
private final String addString;
public Additional(boolean addEnabled, String addString) {
this.addEnabled = addEnabled;
this.addString = addString;
}
}
}
If you leave out the @DefaultValue
in the argument list for argument additional
and you put property my.properties.additional.add-string
in application.properties
in comment, you will notice that the output is:
Value of enabled = false
Value of stringConfig = First test piece
Instead of :
Value of enabled = false
Value of stringConfig = First test piece
Value of additional.addEnabled = false
Value of additional.addString = null
When you run the WebLayerIT
test.
The tests are again identical to the tests of config1.
Conclusion
There are several options in order to configure properties in a Spring Boot application. This post tries to cover some of the options and explains the differences by example. The Constructor Binding configuration is different from the other options because it does not let you modify the properties during runtime.
Which one to choose? In my opinion, the @ConfigurationProperties + @ConfigurationPropertiesScan or the Constructor Binding are the ones to choose from, as they require the least annotations. Constructor Binding is even more safe because the properties cannot be modified during runtime. However, you should always consider the options based on your use case.
Published at DZone with permission of Gunter Rotsaert, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments