Spring Boot @ConfigurationProperties example
Spring Boot @ConfigurationProperties
lets developers map or bind the entire external configuration values in .properties
or .yml
files to Java objects.
Table of contents:
- 1. Access a single value using @Value
- 2. @ConfigurationProperties
- 3. Add JSR303 Validation
- 4. Testing @ConfigurationProperties
- 5. Download Source Code
- 6. References
P.S Tested with Spring Boot 3.1.2
1. Access a single value using @Value
1.1 In Spring Boot, we can use @Value
to access the value from the default application.properties
or application.yml
.
[email protected]
thread-pool=12
@Component
public class GlobalProperties {
@Value("${thread-pool}")
private int threadPool;
@Value("${email}")
private String email;
//getters and setters
}
The equivalent in @ConfigurationProperties
import org.springframework.boot.context.properties.ConfigurationProperties;
@Component
@PropertySource("classpath:global.properties")
@ConfigurationProperties
public class GlobalProperties {
private int threadPool;
private String email;
//getters and setters
}
1.2 Spring Boot loads application.properties
from the root classpath by default. For custom .properties
files (not application.properties
), we need to use @PropertySource
to load the properties, and the access is the same using @Value
.
[email protected]
thread-pool=12
@Component
@PropertySource("classpath:YourName.properties")
public class GlobalProperties {
@Value("${thread-pool}")
private int threadPool;
@Value("${email}")
private String email;
//getters and setters
}
The equivalent in @ConfigurationProperties
import org.springframework.boot.context.properties.ConfigurationProperties;
@Component
@PropertySource("classpath:YourName.properties")
@ConfigurationProperties
public class GlobalProperties {
private int threadPool;
private String email;
//getters and setters
}
2. @ConfigurationProperties
The @Value
is suitable for simple structure configuration files; for complex structures, we can use @ConfigurationProperties
to map or bind the .properties
or yml
configuration values to Java objects.
#Logging
logging.level.org.springframework.web=ERROR
logging.level.com.mkyong=DEBUG
#Global
[email protected]
thread-pool=10
#App
app.menus[0].title=Home
app.menus[0].name=Home
app.menus[0].path=/
app.menus[1].title=Login
app.menus[1].name=Login
app.menus[1].path=/login
app.compiler.timeout=5
app.compiler.output-folder=/temp/
app.error=/error/
or the equivalent in YAML format.
logging:
level:
org.springframework.web: ERROR
com.mkyong: DEBUG
email: [email protected]
thread-pool: 10
app:
menus:
- title: Home
name: Home
path: /
- title: Login
name: Login
path: /login
compiler:
timeout: 5
output-folder: /temp/
error: /error/
package com.mkyong;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
// This component binds values from application.properties to object via @ConfigurationProperties
@Component
@ConfigurationProperties("app") // prefix app, find app.* values
public class AppProperties {
private String error;
private List<Menu> menus = new ArrayList<>();
private Compiler compiler = new Compiler();
public static class Menu {
private String name;
private String path;
private String title;
//getters and setters
}
public static class Compiler {
private String timeout;
private String outputFolder;
//getters and setters
}
//getters and setters
}
package com.mkyong;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties // no prefix, find root level values.
public class GlobalProperties {
private int threadPool; //
private String email;
//getters and setters
}
The Relaxed Data Binding
The @ConfigurationProperties
annotation supports "relaxed binding", meaning properties like threadPool
, thread-pool
are equivalent. Read more about relax binding.
3. Add JSR303 Validation
This @ConfigurationProperties
supports JSR-303 bean validation APIs.
3.1 Add JSR 303 dependencies
<!-- JSR 303 - Bean Validation API -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
</dependency>
<!-- Bean Validation API Reference Implementation -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
3.2 @ConfigurationProperties and @Validated
Add the @Validated
annotation to the @ConfigurationProperties
class and jakarta.validation.constraints.*
annotations on the fields we want to validate.
package com.mkyong;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
// Spring Boot 2
/*import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;*/
// Spring Boot 3
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
@Component
@ConfigurationProperties
@Validated
public class GlobalProperties {
@Max(5)
@Min(0)
private int threadPool;
@NotEmpty
private String email;
//getters and setters
}
Update the thread-pool
to 10, and we should expect an error during the Spring Boot application starts because we have a @Max(5)
on the threadPool
field.
#Global
[email protected]
thread-pool=10
Starts Spring Boot application.
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException:
Failed to bind properties under '' to com.mkyong.global.GlobalProperties failed:
Property: .threadPool
Value: "10"
Origin: class path resource [application.properties] - 7:13
Reason: must be less than or equal to 5
Action:
Update your application's configuration
4. Testing @ConfigurationProperties
Below is a REST Controller endpoint that returns the @ConfigurationProperties
values in JSON format.
package com.mkyong;
import com.mkyong.global.AppProperties;
import com.mkyong.global.GlobalProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MainController {
private static final Logger logger = LoggerFactory.getLogger(MainController.class);
@Autowired
private AppProperties app;
@Autowired
private GlobalProperties global;
// return object in JSON format
@GetMapping("/")
public AppProperties main() {
return app;
}
@GetMapping("/global")
public GlobalProperties global() {
return global;
}
}
For integration tests, we can combine @WebMvcTest
and @EnableConfigurationProperties
to test the configuration values easier.
package com.mkyong;
import com.mkyong.global.AppProperties;
import com.mkyong.global.GlobalProperties;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
// a bit heavy to loads the entire Spring context
// @SpringBootTest
// @AutoConfigureMockMvc
@WebMvcTest(MainController.class)
@EnableConfigurationProperties({AppProperties.class, GlobalProperties.class})
public class MainControllerTest {
@Autowired
private MockMvc mvc;
@Test
public void testGlobalProperties() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/global")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
// has field name `threadPool` of with value of 5
.andExpect(jsonPath("$.threadPool", is(5)))
.andExpect(jsonPath("$.email", is("[email protected]")));
}
@Test
public void testAppProperties() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.error", is("/error/")))
.andExpect(jsonPath("$.compiler.timeout", is("5")))
.andExpect(jsonPath("$.compiler.outputFolder", is("/temp/")))
.andExpect(jsonPath("$.menus[0].title", is("Home")))
.andExpect(jsonPath("$.menus[1].title", is("Login")));
}
}
5. Download Source Code
$ git clone https://github.com/mkyong/spring-boot.git
$ cd spring-boot-externalize-config
$ mvn test
$ mvn spring-boot:run
$ curl localhost:8080
6. References
- SpringDoc – @ConfigurationProperties Validation
- Spring Boot – Externalized Configuration
- JBoss – Hibernate Validator
- Jakarta Bean Validation
- SpringDoc – Java Bean Validation
- Spring @PropertySource example
- javax.validation.constraints does not exist
- Testing JSON in Spring Boot
- How to access values from application.properties in Spring Boot
Nice article! Just a simple question, when you are working with @ConfigurationProperties, the variable names must match the properties keys, right? What about names like “thread-pool” that you have used threadPool, it identify it automatically? Thanks!
yes, In Spring, this called relaxed binding
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-relaxed-binding
Both ‘thread-pool’ and ‘thread_pool’ will map to threadPool property.
Thank you! Really helps!
Thanks for the ‘@Validated’ annotation for enabling field validation. Numerous other blogs have no mention of that.
Had been tirelessly trying to find just that for a couple of days now.