How to Elegantly Bind Custom Configuration Parameters with @ConfigurationProperties
This article explains why using @Value for configuration leads to scattered, unvalidated settings and demonstrates how Spring Boot's @ConfigurationProperties provides batch binding, nested object support, built‑in validation, default values, and starter integration for clean, maintainable configuration management.
Problems with @Value
When a project contains many custom business configurations, middleware parameters, component switches, or environment‑specific settings, using @Value leads to:
Scattered configuration fields, making unified management difficult.
Inability to bind complex types such as List, Map or arrays in bulk.
No built‑in validation, so missing or illegal values cause runtime failures.
Redundant default values that must be specified on each field.
Cannot batch inject into a starter, preventing centralized parameter handling.
Weak type conversion; custom date, enum or special‑format strings require manual conversion.
@ConfigurationProperties – Standard Solution
Spring Boot recommends @ConfigurationProperties for batch binding. It addresses the above issues by:
Encapsulating all related configuration into a single entity class.
Supporting nested objects, collections and arrays.
Providing powerful type conversion and custom Converter support.
Integrating JSR‑380 validation annotations.
Allowing global default values directly in the entity.
Working with @EnableConfigurationProperties for starter‑style auto‑configuration.
Core Annotation Definition
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {
// Configuration prefix for binding
String prefix() default "";
// Enable relaxed binding (default true)
boolean ignoreUnknownFields() default true;
// Fail on unknown fields when false
boolean ignoreInvalidFields() default false;
}Supporting Annotations
@EnableConfigurationProperties– registers configuration entities as beans; required for custom starters. @ConstructorBinding – binds via constructor (recommended since Spring Boot 2.2) for immutable configuration. @Validated – activates JSR‑380 validation; works with annotations like @NotBlank, @Min, @Pattern. @Data / @AllArgsConstructor – Lombok shortcuts for getters, setters and constructors.
Binding Execution Flow
Spring loads environment sources (YAML, properties, system variables, command‑line args).
All classes annotated with @ConfigurationProperties are scanned.
Properties matching the defined prefix are collected.
The Binder utility performs type conversion and relaxed name mapping.
Values are injected via setters or constructor arguments.
If @Validated is present, validation runs and any violation aborts startup.
The bound entity is registered as a singleton bean, ready for @Autowired injection.
Comparison: @Value vs @ConfigurationProperties
Batch binding : @Value binds a single field scattered across the codebase; @ConfigurationProperties binds all properties under a common prefix in a single class.
Nested objects / List / Map : Not supported by @Value; fully supported by @ConfigurationProperties.
Parameter validation : Manual checks with @Value; native JSR‑380 support with @ConfigurationProperties.
Relaxed name mapping : Exact name required for @Value; automatic camel‑case, kebab‑case and underscore mapping for @ConfigurationProperties.
Default values : Specified per field with @Value; defined once in the entity class for @ConfigurationProperties.
Starter integration : @Value cannot batch inject; @ConfigurationProperties is the standard starter pattern.
Type conversion : Manual for custom types with @Value; built‑in converters plus custom Converter support with @ConfigurationProperties.
IDE metadata : No auto‑completion for @Value; with spring-boot-configuration-processor, IDE shows hints for @ConfigurationProperties.
Four Common Usage Patterns
1. @Component + @ConfigurationProperties
Add @Component to the configuration class; Spring scans and binds automatically.
thread:
pool:
core-size: 8
max-size: 16
queue-capacity: 500
keep-alive: 60
enable: true
name-prefix: business-thread @Data
@Component
@ConfigurationProperties(prefix = "thread.pool")
public class ThreadPoolProperties {
private Integer coreSize = 5;
private Integer maxSize = 10;
private Integer queueCapacity = 200;
private Long keepAlive = 30L;
private Boolean enable = false;
private String namePrefix = "default-pool";
}Inject with @Autowired wherever needed.
2. @EnableConfigurationProperties Registration
Leave the entity without @Component and register it in a configuration class.
@Data
@ConfigurationProperties(prefix = "thread.pool")
public class ThreadPoolProperties { /* fields omitted for brevity */ } @Configuration
@EnableConfigurationProperties(ThreadPoolProperties.class)
public class ThreadPoolAutoConfig {
@Bean
public ExecutorService businessThreadPool(ThreadPoolProperties p) {
return new ThreadPoolExecutor(p.getCoreSize(), p.getMaxSize(), p.getKeepAlive(),
TimeUnit.SECONDS, new LinkedBlockingQueue<>(p.getQueueCapacity()));
}
}3. Constructor Binding with @ConstructorBinding
Immutable configuration using a constructor; no setters required.
@Getter
@AllArgsConstructor
@ConstructorBinding
@ConfigurationProperties(prefix = "thread.pool")
public class ThreadPoolProperties {
private final Integer coreSize;
private final Integer maxSize;
private final String namePrefix;
// defaults can be set in the constructor arguments
}4. @Bean Method Binding
Bind a temporary configuration directly to a @Bean method parameter.
@Configuration
public class TempConfig {
@Bean
@ConfigurationProperties(prefix = "temp.demo")
public DemoConfig demoConfig() {
return new DemoConfig();
}
}Complex Nested Binding (List, Map, Arrays, Inner Classes)
YAML example for an OSS configuration:
custom:
oss:
enable: true
endpoint: oss-cn-jd.com
access-key: AK123456
secret-key: SK666888
bucket-list:
- image-bucket
- file-bucket
bucket-map:
image: img-bucket
doc: file-bucket
policy:
expire-day: 7
max-size: 10485760Corresponding entity with a nested static class:
@Data
@Component
@ConfigurationProperties(prefix = "custom.oss")
public class OssProperties {
private Boolean enable;
private String endpoint;
private String accessKey;
private String secretKey;
private List<String> bucketList;
private Map<String, String> bucketMap;
private Policy policy;
@Data
public static class Policy {
private Integer expireDay;
private Long maxSize;
}
}Advanced Feature 1 – JSR‑380 Validation
Annotate fields with validation constraints and add @Validated on the class. Invalid values cause startup failure.
@Data
@Component
@Validated
@ConfigurationProperties(prefix = "thread.pool")
public class ThreadPoolProperties {
@Min(1) @Max(50)
private Integer coreSize = 5;
@Min(1)
private Integer maxSize = 10;
@NotNull
private Integer queueCapacity = 200;
@Min(10)
private Long keepAlive = 30L;
@NotBlank
private String namePrefix = "business-pool";
}Example error output when core-size is set to 0:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'thread.pool' to com.demo.properties.ThreadPoolProperties
Property: thread.pool.core-size
Value: 0
Reason: core thread count cannot be less than 1Advanced Feature 2 – Relaxed Binding Rules
By default, @ConfigurationProperties maps core-size, core_size and coreSize to the same field. To disable, set ignoreUnknownFields = false or adjust ignoreInvalidFields as needed.
Advanced Feature 3 – Custom Type Converters
Built‑in converters handle common types. For enums, dates or special formats, implement Converter<S, T>.
public enum OssType { ALIYUN, TENCENT, HUAWEI }
// YML
custom.oss.type: ALIYUN
// Entity field
private OssType type;
// Optional case‑insensitive converter
@Component
public class OssTypeConverter implements Converter<String, OssType> {
@Override
public OssType convert(String source) {
return OssType.valueOf(source.toUpperCase());
}
}Starter Template – Full‑Stack Configuration
Define a property class, an auto‑configuration class, and register via spring.factories (or the newer AutoConfiguration.imports file).
@Data
@ConfigurationProperties(prefix = "custom.thread")
@Validated
public class ThreadStarterProperties {
@Min(1)
private Integer coreSize = 5;
private Integer maxSize = 10;
private Boolean enable = true;
}
@AutoConfiguration
@EnableConfigurationProperties(ThreadStarterProperties.class)
@ConditionalOnClass(ThreadPoolExecutor.class)
@ConditionalOnProperty(prefix = "custom.thread", name = "enable", matchIfMissing = true)
public class ThreadPoolAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ExecutorService customThreadPool(ThreadStarterProperties p) {
return new ThreadPoolExecutor(p.getCoreSize(), p.getMaxSize(), 60L,
TimeUnit.SECONDS, new LinkedBlockingQueue<>());
}
}Register the auto‑configuration in
resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
com.demo.autoconfigure.ThreadPoolAutoConfigurationCommon Pitfalls and Solutions
Binding does not work – often caused by missing @EnableConfigurationProperties, wrong prefix, missing setters, or absent spring-boot-configuration-processor dependency.
List/Map remains empty – check YAML indentation, dash syntax, and ensure setters or constructor binding are present.
Validation not triggered – add @Validated and include spring-boot-starter-validation.
@Value works but @ConfigurationProperties does not – verify relaxed binding is enabled and prefix matches the YAML hierarchy.
Multi‑environment overrides ignored – ensure the correct profile is active via spring.profiles.active.
Constructor binding errors – add @ConstructorBinding, provide a full‑arg constructor (Lombok @AllArgsConstructor works), and avoid Lombok setter generation.
IDE shows no YML hints – add the optional spring-boot-configuration-processor dependency and run mvn compile to generate metadata.
Extra fields silently ignored – set ignoreUnknownFields = false to fail fast on unknown properties.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Tech Workshop
Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
