configprops
ConfigProps: The Ultimate Guide to Spring Boot Externalized Configuration
Key Takeaways
- ConfigProps (short for Configuration Properties) is Spring Boot's mechanism for binding external configuration to strongly-typed Java objects.
- Using
@ConfigurationPropertieseliminates magic strings and provides compile-time safety for configuration values. - Always use constructor binding (
@ConstructorBinding) for immutable, thread-safe configuration classes. - Validate configuration properties at startup using JSR-303 annotations like
@NotNulland@Min. - Enable configuration property processing with
@EnableConfigurationPropertiesor the configuration processor annotation.
What Is ConfigProps and Why Should You Care?
ConfigProps refers to Spring Boot's @ConfigurationProperties feature, which allows developers to externalize application configuration into type-safe Java beans. Instead of scattering environment.getProperty("database.url") calls throughout your codebase, ConfigProps lets you map entire configuration namespaces to dedicated classes.
This approach solves several critical pain points:
- Magic Strings: Eliminates hard-coded property keys that break silently during refactoring.
- Type Safety: Compile-time validation prevents runtime errors from misconfigured values.
- IDE Support: Autocompletion and navigation make configuration management effortless.
- Validation: Built-in JSR-303 support catches invalid configurations before deployment.
How Do I Implement ConfigProps in My Spring Boot Application?
Step 1: Define Your Configuration Properties Class
@ConfigurationProperties(prefix = "app.datasource")
@ConstructorBinding
public class DataSourceProperties {
@NotBlank
private final String url;
@NotBlank
private final String username;
@NotBlank
private final String password;
private final Integer maxPoolSize = 10;
private final Duration connectionTimeout = Duration.ofSeconds(30);
public DataSourceProperties(String url, String username, String password,
Integer maxPoolSize, Duration connectionTimeout) {
this.url = url;
this.username = username;
this.password = password;
this.maxPoolSize = maxPoolSize != null ? maxPoolSize : 10;
this.connectionTimeout = connectionTimeout != null ? connectionTimeout : Duration.ofSeconds(30);
}
// Getters only for immutability
public String getUrl() { return url; }
public String getUsername() { return username; }
public String getPassword() { return password; }
public Integer getMaxPoolSize() { return maxPoolSize; }
public Duration getConnectionTimeout() { return connectionTimeout; }
}
Step 2: Register the Configuration Properties
You have two options for registration:
Option A: Component Scanning (Recommended)
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
public class AppConfig {
// Configuration beans here
}
Option B: Direct Annotation
@ConfigurationProperties(prefix = "app.datasource")
@Component
@ConstructorBinding
public class DataSourceProperties {
// ... same as above
}
Step 3: Use Properties in application.yml
app:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: myuser
password: ${DB_PASSWORD:secret}
max-pool-size: 20
connection-timeout: 45s
Step 4: Inject and Use in Services
@Service
public class DatabaseService {
private final DataSourceProperties dataSourceProperties;
public DatabaseService(DataSourceProperties dataSourceProperties) {
this.dataSourceProperties = dataSourceProperties;
}
public void connect() {
System.out.println("Connecting to: " + dataSourceProperties.getUrl());
// Use strongly-typed properties safely
}
}
What Are the Best Practices for ConfigProps?
Constructor Binding Over Setter Binding
Always prefer @ConstructorBinding over setter-based binding:
| Feature | Constructor Binding | Setter Binding |
|---|---|---|
| Thread Safety | Immutable - fully thread-safe | Mutable - requires synchronization |
| Null Safety | Fail-fast on missing required properties | Silent null values possible |
| Validation | JSR-303 works reliably | Validation timing is inconsistent |
| Performance | Better performance due to immutability | Reflection overhead on every set |
| Recommended For | All production applications | Legacy migration only |
Validation Checklist
- [ ] Use
@ConstructorBindingfor immutability - [ ] Apply JSR-303 annotations (
@NotBlank,@NotNull,@Min,@Max) - [ ] Set sensible defaults in constructors
- [] Use
@DefaultValuefor optional properties - [ ] Enable configuration property processing with
spring-boot-configuration-processor - [ ] Document properties with
@ConfigurationPropertiesScan - [ ] Validate at startup using
@Validated
How Do I Handle Complex Nested Configuration?
ConfigProps supports complex nested structures seamlessly:
@ConfigurationProperties(prefix = "app.services")
@ConstructorBinding
public class ServicesProperties {
private final List<ServiceConfig> endpoints;
private final Map<String, ServiceConfig> namedServices;
public ServicesProperties(List<ServiceConfig> endpoints,
Map<String, ServiceConfig> namedServices) {
this.endpoints = endpoints != null ? endpoints : Collections.emptyList();
this.namedServices = namedServices != null ? namedServices : Collections.emptyMap();
}
public static class ServiceConfig {
@NotBlank
private final String url;
@Min(1)
private final int timeoutSeconds;
private final List<String> allowedIps;
public ServiceConfig(String url, int timeoutSeconds, List<String> allowedIps) {
this.url = url;
this.timeoutSeconds = timeoutSeconds;
this.allowedIps = allowedIps != null ? allowedIps : Collections.emptyList();
}
// Getters...
}
}
Comparison: ConfigProps vs Environment vs @Value
| Aspect | @ConfigurationProperties | Environment | @Value |
|---|---|---|---|
| Type Safety | Full compile-time safety | Runtime string-based | Limited type conversion |
| Refactoring Support | Full IDE support | No refactoring support | Partial IDE support |
| Validation | Built-in JSR-303 support | Manual validation required | Manual validation required |
| Boilerplate Code | Minimal after setup | Moderate | Low but scattered |
| Best Use Case | Large configuration blocks | Dynamic runtime properties | Simple single values |
FAQs
Can I Use ConfigProps with Spring Cloud Config?
Yes. ConfigProps works seamlessly with Spring Cloud Config. When using a centralized configuration server, your @ConfigurationProperties classes automatically pick up values from remote property sources. The configuration processor metadata generated by spring-boot-configuration-processor ensures that your IDE and documentation tools understand properties sourced from external config servers.
How Do I Debug Configuration Property Binding Issues?
Enable debug logging by adding debug=true to your application.properties or setting logging level for org.springframework.boot.context.properties to DEBUG. This reveals which properties are being bound and any binding failures. Additionally, use @ConfigurationPropertiesScan in your main application class to ensure all configuration properties classes are discovered.
Should I Generate Configuration Metadata for My Properties?
Absolutely yes. Add spring-boot-configuration-processor as an annotation processor dependency in your build configuration. This generates .md metadata files that power IDE autocompletion, the Actuator /configprops endpoint, and configuration documentation tools like Spring Boot's configuration report.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>