In this article, we are going to shed light on Spring Boot configuration properties.

First, we will start with a little bit of background on what configuration properties are. Then, we will highlight how to bind simple and nested properties using @Value and @ConfigurationProperties.

Finally, we are going to showcase how to bind profile-specific parameters.

What are Configuration Properties?

Basically, config properties denote the list of parameters that define an application setting.

Spring Boot offers several ways to externalize these props. For example, we can use YAML or .properties files to define them.

The basic idea is to adapt the application to work in different environments.

Injecting Simple Properties using @Value

Typically, we can use the @Value annotation to inject a property value directly into a spring bean.

As a matter of fact, this annotation is introduced to read simple properties one by one.

For example, let’s externalize the name of our application in application.yml:

    
        app:
           name: devwithus
           description: devwithus.com
    

Now, we will illustrate how to bind app.name to a spring bean field:

    
        @Component
        public class AppPropsConfig {
            @Value("${app.name}")
            private String appName;
            
            public String getAppName() {
                return appName;
            }
            public void setAppName(String appName) {
                this.appName = appName;
            }
        }
    

Properties Binding in Spring Boot 2.1

Another solution would be using the built-in @ConfigurationProperties annotation provided by Spring Boot.

The idea behind this annotation is to bind hierarchical properties to a POJO. We already used this annotation to bind file upload properties.

@ConfigurationProperties Example

For instance, let’s assume we want to send emails using the Gmail SMTP server:

    
        gmail:
            smtp:
                host: smtp.gmail.com
                port: 587
                username: admin
                password: P@ssW@rd
    

Next, we are going to demonstrate how to use @ConfigurationProperties to map our Gmail SMTP parameters:

    
        @ConfigurationProperties(prefix = "app.gmail.smtp")
        public class GmailSmtpServer {
            private String host;
            private int port;
            private String username;
            private String password;

            public String getHost() {
                return host;
            }
            public void setHost(String host) {
                this.host = host;
            }

            public int getPort() {
                return port;
            }
            public void setPort(int port) {
                this.port = port;
            }

            public String getUsername() {
                return username;
            }
            public void setUsername(String username) {
                this.username = username;
            }

            public String getPassword() {
                return password;
            }
            public void setPassword(String password) {
                this.password = password;
            }
        }
    

As we can see, @ConfigurationProperties provides the prefix attribute. It denotes the starting point of the properties binding.

For example, Spring Boot will map app.gmail.smtp.host property to host variable.

Spring Boot relies on setters to do the all the heavy lifting of the mapping. This is why we define them for each field.

Please note that we need to annotate the main entry class with @EnableConfigurationProperties to enable the binding or use @ConfigurationProperties in conjunction with @Configuration.

Now that we put all the pieces together, let’s check that everything is working as expected:

    
        @SpringBootApplication
        @EnableConfigurationProperties
        public class GmailSmtpApplication implements CommandLineRunner {
            @Autowired
            private ApplicationContext applicationContext;
            public static void main(String[] args) {
                SpringApplication.run(GmailSmtpApplication.class, args);
            }
            @Override
            public void run(String... args) throws Exception {
                GmailSmtpServer myBean = applicationContext.getBean(GmailSmtpServer.class);
                System.out.println("SMTP Host: "+myBean.getHost());
                System.out.println("SMTP Username: "+myBean.getUsername());
                System.out.println("SMTP Password: "+myBean.getPassword());
            }
        }
    

The above code produces the following output:

Read Simple Configuration Properties

As we can see in the log, Spring Boot successfully reads the application properties defined in application.yml.

Reading Properties with Different Names

By design, Spring uses relaxed mapping strategy to match property names with the java field names.

So, in case we want to use a java field with a different name, then we need to create a setter with an argument that matches the property name pattern.

    
        private String smtpHost;
        public void setHost(String host) {
            this.smtpHost = host;
        }
    

Bind Complex Properties with @ConfigurationProperties

Now that we know how to read simple properties, let’s dig deep and see how to fetch complex and nested configurations.

Spring Boot provides a convenient way to externalize complex data such as Lists, Maps, and user-defined classes. For example, we can use ”-” to define a List in YAML.

Let’s illustrate this using a practical example.

    
        server:
              hostname: devwithus
              ip: 10.10.10.10
              account:
                     login: root
                     password: p@ssw@rd
              services:
                      - ssh
                      - http
                      - smtp
                      - iptables        
    

As we can see, the server is defined by a hostname, ip, account, and a list of services.

Next, we are going to map the server detail into a POJO using the @ConfigurationProperties annotation.

First, let’s consider the Account class:

    
        public class Account {
            private String login;
            private String password;

            public String getLogin() {
                return login;
            }
            public void setLogin(String login) {
                this.login = login;
            }

            public String getPassword() {
                return password;
            }
            public void setPassword(String password) {
                this.password = password;
            }
        }
    

Now, we will bind our server into the ServerPopsConfig bean:

    
        @Component
        @ConfigurationProperties(prefix = "server")
        public class ServerPropsConfig {
            private String hostname;
            private String ip;
            private Account account;
            private List<String> services;
            // Getters and Setters
        }
    

Finally, let’s read the server ip, the account login, and the service list:

Read Nested properties

Properties Validation

@ConfigurationProperties provides support for JSR-303 validation.

For instance, let’s make the server hostname mandatory and validate the IP address (IPv4) using a regex:

    
        @Validated
        public class ServerPropsConfig {
            @NotBlank
            private String hostname;
            @Pattern(regexp = "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\\.(?!$)|$)){4}$")
            private String ip;
            // ...
        }
    
  • @Validated enables the bean validation

  • @NotBlank validates that the hostname is not blank

  • @Pattern compiles the IPV4 regex against the ip value

Please bear in mind that the application will fail with BindException if the validation fails.

For example, let’s define this invalid IP 01.10.10.10 in application.yml.

Now, if we run the application, we will get this error:

    
        Binding to target org.springframework.boot.context.properties.bind.BindException: 
        Failed to bind properties under 'server' to ServerPropsConfig failed:
            Property: server.ip
            Value: 01.10.10.10
            Origin: class path resource [application.yml] - 12:11
            Reason: must match "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.(?!$)|$)){4}$"
    

Please note that we need to add hibernate-validator dependency or another JSR-303 implementation to pom.xml.

Properties Conversion

@ConfigurationProperties comes with another great feature. It supports the conversion of multiple data types such as Duration.

For instance, let’s set a connection time-out to 10 seconds for our server:

    
        server:
              connectionTimeOut: 10s
    

Please notice that we suffixed connectionTimeOut with “s” to tell Spring Boot to convert the value in seconds.

Now, let’s add the corresponding field in ServerPopsConfig:

    
        private Duration connectionTimeOut;
    

As shown above, the connectionTimeOut property is converted to a Duration object without using any custom converters.

Spring Boot 2.2 New Features

Spring Boot 2.2 comes with two new annotations: @ConfigurationPropertiesScan and @ConstructorBinding

So, let’s take a close look at each annotation and see what enhancements bring in.

Using @ConfigurationPropertiesScan

This annotation is introduced to enable scanning for classes marked with the @ConfigurationProperties annotation.

This means that we don’t need to mark configuration property classes with @Component or EnableConfigurationProperties anymore.

Let’s see it in action:

    
        @ConfigurationPropertiesScan("com.devwithus.configs")
        public class ProductmsApplication {
        }
    

@ConfigurationPropertiesScan allows us to scan a specific package. That way, we tell Spring to find and register only classes defined in a particular package.

Using @ConstructorBinding

The main purpose of this annotation is to indicate that properties should be mapped using constructors rather than setters.

We can use it in conjunction with ConfigurationProperties at the type level:

    
        @ConfigurationProperties(prefix = "app")
        @ConstructorBinding
        public class AppPropsConfig {
            private String name;
            private String description;

            public AppPropsConfig(String name, String description) {
                this.name = name;
                this.description = description;
            }
        }
    

Here, @ConstructorBinding relies on the constructor with parameters to do the mapping for each property.

We cannot use the constructor binding on beans marked with @Component or created using @Bean methods

Typically, the constructor binding strategy helps us to make the application properties class immutable. Hence, the absence of the setter methods.

Java 16 Records

In short, record denotes a class that acts as a holder for immutable data. It’s conceptually similar to a class annotated with Lombok @Value annotation.

Now, let’s see how we can rewrite the AppPropsConfig class using record:

    
        @ConfigurationProperties(prefix = "app")
        @ConstructorBinding
        public record AppPropsConfig(String name, String description) {
        }
    

Obviously, a record is a perfect choice to create a configuration holder. It’s more straightforward to read compared to all other approaches.

By default, records come with a canonical constructor and getters.

Reading Custom File Properties

Basically, Spring Boot automatically loads the properties for the default file application.yml.

Please note that Spring Boot reads profile-specific properties from application-{profile}.yml

So, to override this behavior and read from a custom file, we need to use @PropertySource.

We can use it in conjunction with @ConfigurationProperties and @Configuration:

    
        @Configuration
        @PropertySource("classpath:custom.yml")
        @ConfigurationProperties(prefix = "app")
        public class AppPropsConfig {
        }
    

The classpath attribute denotes the location of the custom file.

Difference Between @ConfigurationProperties and @Value

Now, let’s highlight the key differences between @ConfigurationProperties and @Value:

@ConfigurationProperties @Value
used on type level used only on property level
doesn't support SpEL expressions provides support for SpEL
used to map multiple hierarchical properties binds only one property
supports relaxed binding doesn't support relaxed binding
fail-safe, it ignores the property if it doesn't exist throws exception if there no matching placeholder

Conclusion

To sum it up, we covered in-depth Spring Boot configuration properties.

We explored different ways to bind application properties. Along the way, we explained how @ConfigurationProperties differs from @Value.