@Value
in Favor of @ConfigurationProperties
in Spring BootWhen 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!
@Value
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;
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.
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… 🥲
@ConfigurationProperties
🎉🎉@ConfigurationProperties
allows you to bind entire groups of properties to a class. This is much cleaner, type-safe, and easier to manage.
@ConfigurationProperties
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;
}
}
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
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();
}
}
@ConfigurationProperties
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.
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.
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;
}
}
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
}
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!