Main Tutorials

Spring Boot @ConfigurationProperties example

Spring Boot ConfigurationProperties

Spring Boot @ConfigurationProperties lets developers map or bind the entire external configuration values in .properties or .yml files to Java objects.

Table of contents:

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.

application.properties

[email protected]
thread-pool=12
GlobalProperties.java

@Component
public class GlobalProperties {

    @Value("${thread-pool}")
    private int threadPool;

    @Value("${email}")
    private String email;

    //getters and setters

}

The equivalent in @ConfigurationProperties

GlobalProperties.java

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.

YourName.properties

[email protected]
thread-pool=12
GlobalProperties.java

@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

GlobalProperties.java

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.

application.properties

#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.

application.yml

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/
AppProperties.java

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
}
GlobalProperties.java

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

pom.xml

  <!-- 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.

GlobalProperties.java

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.

application.properties

#Global
[email protected]
thread-pool=10

Starts Spring Boot application.

Terminal

***************************
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.

MainController.java

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.

MainControllerTest.java

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

About Author

author image
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.

Comments

Subscribe
Notify of
4 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Jeff
7 years ago

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!

mkyong
7 years ago
Reply to  Jeff

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.

Jeff
7 years ago
Reply to  mkyong

Thank you! Really helps!

Sudarshan K J
4 years ago

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.