Foundation · Configure
Type-Safe Configuration with @ConfigurationProperties
Group related settings into a validated, type-safe class instead of scattering @Value strings through the codebase.
Primary source: Spring Boot 3.5 Reference: Externalized Configuration
Lesson 2 of 4
1d streakThe Architectural Question
Dozens of scattered @Value annotations hide which settings belong together, skip validation until a request fails, and make every rename a string hunt across the codebase.
What this guide proves
- Bind nested configuration blocks to records with relaxed binding
- Validate configuration at startup before requests arrive
- Reserve @Value for single, trivial lookups
Type-Safe Configuration with @ConfigurationProperties Records
Injecting individual properties with @Value("${property.key}") creates brittle architectures: keys are hardcoded strings, syntax errors fail only when individual beans are accessed, and there is no centralized schema. In modern Spring Boot (3.0+), the gold standard is @ConfigurationProperties paired with immutable Java records and constructor binding.
package com.example.config;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import java.time.Duration;
@ConfigurationProperties(prefix = "app.mail")
@Validated
public record MailProperties(
@NotBlank String host,
@Min(1) @Max(65535) int port,
@NotBlank String username,
@NotBlank String password,
@NotNull Duration timeout,
boolean starttls
) {
public MailProperties {
if (timeout == null) {
timeout = Duration.ofSeconds(5);
}
}
}By enabling @EnableConfigurationProperties(MailProperties.class) or relying on @ConfigurationPropertiesScan, Spring Boot discovers the record, binds properties matching the app.mail prefix directly to constructor arguments, and registers the immutable record as a singleton bean.
Active Recall Checkpoint
Why are Java records ideal for @ConfigurationProperties?
Hint: Think about immutability, thread-safety, and boilerplate reduction.
Show answer & explanation
Records are shallowly immutable, have concise canonical constructors, generate getters automatically, and cannot be mutated at runtime by rogue application code, making them thread-safe configuration carriers.
Fail-Fast Validation with Jakarta Validation
Annotating a @ConfigurationProperties class or record with @Validated instructs Spring Boot to execute the Jakarta Validation engine (Hibernate Validator) during application startup. If any constraint is violated (e.g. port is 0 or host is blank), Boot aborts startup immediately with a clear error report before any HTTP requests or database connections are opened.
| Constraint Annotation | Target Field Type | Enforced Rule |
|---|---|---|
| @NotBlank | String | String must not be null and must contain at least one non-whitespace character |
| @Min / @Max | int, long, BigDecimal | Numeric value must lie within the specified inclusive bounds |
| @Pattern(regexp=...) | String | String must conform to the specified regular expression (e.g. URL, ARN) |
| @NotNull | Duration, DataSize, Object | Field must not be null; guarantees safe unboxing |
| @Valid | Nested record / class | Recursively cascades validation into child configuration records |
Nested Record Validation
public record SshConfig(@Valid KeyConfig keys, @NotNull String user)), you MUST annotate the nested field with @Valid. Without @Valid, constraints declared inside KeyConfig will be completely ignored!Active Recall Checkpoint
What happens if a required property has no default and is missing from all configuration files when @Validated and @NotNull are present?
Hint: When does validation occur in the Spring Boot lifecycle?
Show answer & explanation
Spring Boot throws a BindValidationException wrapped in a ConfigurationPropertiesBindException during the early context refresh phase, preventing the application from starting in an invalid state.
Conversion Services: Durations, DataSizes & Custom Converters
Spring Boot contains an advanced ApplicationConversionService that converts plain string properties into rich domain types without manual parsing.
| String Value in YAML | Target Java Type | Interpreted Value |
|---|---|---|
| 10s, 500ms, 2m, 1h | java.time.Duration | Parsed as Duration.ofSeconds(10), Duration.ofMillis(500), etc. |
| 10MB, 1GB, 512KB | org.springframework.util.unit.DataSize | Parsed as DataSize.ofMegabytes(10), etc. |
| https://api.stripe.com | java.net.URI or URL | Parsed into a validated URI instance |
| US, DE, IN | java.util.Locale | Parsed into Locale.US, Locale.GERMANY, etc. |
| ACTIVE, PENDING | Java Enum | Case-insensitive matching to enum constants |
Always Prefer Duration over int timeoutMillis
int timeout or long timeoutMillis. Declaring fields as Duration allows developers to write timeout: 30s in YAML, eliminating unit confusion bugs where 30 seconds was mistakenly entered as 30 milliseconds.Active Recall Checkpoint
What is the advantage of binding app.cache.max-size: 50MB to DataSize instead of long bytes?
Hint: Consider readability and risk of calculation errors in configuration.
Show answer & explanation
It prevents calculation errors (such as multiplying by 1000 instead of 1024) and makes configuration human-readable, while the DataSize object provides .toBytes() and .toMegabytes() helper methods for code consumption.
Deliberate Failure Lab: Diagnosing ConfigurationPropertiesBindException
Let us observe what happens when a property fails validation. Suppose app.mail.port is set to -1 or omitted when annotated with @Min(1).
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.springframework.boot.context.properties.bind.BindResult@7f3b84 failed:
Property: app.mail.port
Value: -1
Origin: class path resource [application.yml] - 4:11
Reason: must be greater than or equal to 1
Action:
Update your application's configurationPinpointing the Failure Origin
app.mail.port), the offending value (-1), the exact origin file and line number (application.yml - 4:11), and the Jakarta Validation reason (must be greater than or equal to 1). Always read the Origin line when debugging config crashes.Active Recall Checkpoint
How do you generate IDE metadata (auto-completion and documentation tooltips in IntelliJ/VS Code) for your custom @ConfigurationProperties classes?
Hint: What build dependency generates the spring-configuration-metadata.json file?
Show answer & explanation
Add the org.springframework.boot:spring-boot-configuration-processor annotation processor to your Maven or Gradle build. During compilation, it analyzes your classes and emits META-INF/spring-configuration-metadata.json for IDE autocompletion.
Runtime Architecture & Flow
Resolution model
Configuration resolution checklist
- 1
Bind nested configuration blocks to records with relaxed binding
- 2
Validate configuration at startup before requests arrive
- 3
Reserve @Value for single, trivial lookups
Inspect the effective application state before assuming which declaration won.
Synthesis & Production Verification
Production Acceptance Criteria
- Bind a two-level YAML block to an immutable record
- Trigger a startup failure with an invalid value
- Explain kebab-case to camelCase relaxed binding with an example
Key Takeaway
@ConfigurationProperties paired with immutable Java records provides type-safe, validated, and cohesive configuration classes that fail fast at startup when invalid.
Architectural & Technical Review
Core Interview Questions
Q01Why is @ConfigurationProperties preferred over injecting individual @Value annotations?Show answer
@ConfigurationProperties centralizes related properties into strongly typed, immutable Java records, provides IDE autocompletion, supports relaxed binding, and enables fail-fast validation with Jakarta Validation at startup.
Q02How does @Validated enforce fail-fast configuration checks during container bootstrap?Show answer
Annotating a @ConfigurationProperties record with @Validated triggers Jakarta Validation (Hibernate Validator) during context refresh. Missing required fields or boundary violations throw BindValidationException and halt startup before traffic is served.
Q03How does ApplicationConversionService handle rich types like Duration and DataSize?Show answer
It automatically converts human-readable strings like "10s", "500ms", and "50MB" into java.time.Duration and org.springframework.util.unit.DataSize instances, eliminating unit ambiguity bugs.
Production Pitfalls
Critical Anti-Patterns
- Pitfall: Forgetting @Valid on nested configuration records — Jakarta Validation does not cascade into child configuration records unless the field is annotated with @Valid.
- Pitfall: Using @Value for complex configuration structures — Scattering @Value across classes creates fragile strings, lacks validation schemas, and complicates testing.
Primary Documentation & Specifications
Lesson Primary References
Supplementary Module Resources