←TIL

@ConfigurationProperties > @Values

August 2, 2024

Why You Should Ditch @Value in Favor of @ConfigurationProperties in Spring Boot

Introduction

When working with Spring Boot, it’s common to externalize your configurations in properties files (like application.yml or application.properties). The most widely known way to inject these properties into your application (atleast to me) is by using the @Value annotation.

I read somewhere about the problems with @Value and why switching to @ConfigurationProperties is a much better approach for type-safe, maintainable, and scalable configurations, so this serves as an exploration of that!

The alleged problems with @Value

  1. Scattered Configurations - Totally agree: @Value is often applied directly on class fields, leading to scattered configuration references all over your code. This makes it harder to track where a certain configuration is being used.

    @Value("${app.name}")
    private String appName;
    
    @Value("${app.timeout}")
    private int timeout;
    
  2. Lack of Type-Safety - Been there. Done that. It’s painful: With @Value, you lose type safety. For example, Spring will attempt to convert the value into the appropriate type (like int, boolean, etc.), but there’s no way to validate the structure as a whole.

  3. No Support for Nested Configurations - : If you have a complex hierarchical configuration structure, @Value becomes unwieldy. For example:

   app.email.host=smtp.example.com
   app.email.port=587
   app.email.username=user
   app.email.password=secret

While this is something which can be solved easily with yaml files, they’re definitely better looking than this, with @Value, you’d need to inject each property separately. This results in boilerplate code and a messy setup.

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

@Value("${app.email.port}")
private int port;

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

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

Notice the app.email being repeated again… and again… and again… 🥲

The Solution: @ConfigurationProperties 🎉🎉

@ConfigurationProperties allows you to bind entire groups of properties to a class. This is much cleaner, type-safe, and easier to manage.

How to Use @ConfigurationProperties

1. Define the Configuration Class

First, create a class that will hold the configuration. This class will be annotated with @ConfigurationProperties and @Component (or register it as a bean). Let’s say we have an email configuration:

package com.example.demo.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "app.email")
public class EmailConfig {

    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;
    }
}

2. Define the Properties in application.yml

Next, define your configuration in application.yml (or application.properties if you prefer, you should not prefer, but who am I to judge):

app:
  email:
    host: smtp.example.com
    port: 587
    username: user
    password: secret

3. Use the Configuration Class in Your Application

Now, you can simply inject the EmailConfig class wherever you need the email configuration. No need to inject individual properties using @Value anymore.

package com.example.demo;

import com.example.demo.config.EmailConfig;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EmailController {

    private final EmailConfig emailConfig;

    public EmailController(EmailConfig emailConfig) {
        this.emailConfig = emailConfig;
    }

    @GetMapping("/email-config")
    public String getEmailConfig() {
        return "Host: " + emailConfig.getHost() + ", Port: " + emailConfig.getPort();
    }
}

Advantages of @ConfigurationProperties

  1. Type-Safe Configuration: With @ConfigurationProperties, the properties are bound directly to fields, ensuring type safety. If there’s a mismatch between the configuration file and the Java type, you’ll get errors at startup, preventing runtime issues.

  2. Cleaner Code with Grouped Configurations: Instead of scattering @Value annotations all over your code, you have one configuration class that neatly groups related properties together. This results in cleaner and more maintainable code.

  3. Support for Nested and Complex Configurations: @ConfigurationProperties supports nested structures and collections, making it perfect for handling complex configuration files.

    For example, you can define a list of servers like this:

    app:
      servers:
        - url: https://server1.example.com
          port: 8080
        - url: https://server2.example.com
          port: 9090
    

    And bind it to a list in your configuration class:

    @Component
    @ConfigurationProperties(prefix = "app")
    public class AppConfig {
        private List<Server> servers;
    
        public static class Server {
            private String url;
            private int port;
    
        }
    
        public List<Server> getServers() {
            return servers;
        }
    
        public void setServers(List<Server> servers) {
            this.servers = servers;
        }
    }
    
  4. Validation Support: You can also add validation to your configuration properties by using @Valid and JSR-303 annotations, ensuring that your configuration files always contain valid values.

    @Component
    @ConfigurationProperties(prefix = "app.email")
    public class EmailConfig {
    
        @NotNull
        private String host;
    
        @Min(1)
        @Max(65535)
        private int port;
    
        // Other fields, getters, and setters
    }
    

Conclusion

Switching from @Value to @ConfigurationProperties is a step towards writing more robust, maintainable, and type-safe Spring Boot applications. By grouping configurations in a single class, ensuring type safety, and supporting complex structures, @ConfigurationProperties gives you more control and flexibility over your application’s configuration.

Next time you find yourself sprinkling @Value all over your code, consider giving @ConfigurationProperties a try—you’ll thank yourself later!


spring-boot