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

25 min

Lesson 2 of 4

1d streak

The 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.

MailProperties.java
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 AnnotationTarget Field TypeEnforced Rule
@NotBlankStringString must not be null and must contain at least one non-whitespace character
@Min / @Maxint, long, BigDecimalNumeric value must lie within the specified inclusive bounds
@Pattern(regexp=...)StringString must conform to the specified regular expression (e.g. URL, ARN)
@NotNullDuration, DataSize, ObjectField must not be null; guarantees safe unboxing
@ValidNested record / classRecursively cascades validation into child configuration records

Nested Record Validation

When your configuration record contains nested objects (e.g. 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 YAMLTarget Java TypeInterpreted Value
10s, 500ms, 2m, 1hjava.time.DurationParsed as Duration.ofSeconds(10), Duration.ofMillis(500), etc.
10MB, 1GB, 512KBorg.springframework.util.unit.DataSizeParsed as DataSize.ofMegabytes(10), etc.
https://api.stripe.comjava.net.URI or URLParsed into a validated URI instance
US, DE, INjava.util.LocaleParsed into Locale.US, Locale.GERMANY, etc.
ACTIVE, PENDINGJava EnumCase-insensitive matching to enum constants

Always Prefer Duration over int timeoutMillis

Never write 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).

startup-failure.log
***************************
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 configuration

Pinpointing the Failure Origin

Spring Boot's failure analyzer explicitly names the faulty property (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. 1

    Bind nested configuration blocks to records with relaxed binding

  2. 2

    Validate configuration at startup before requests arrive

  3. 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

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

Supplementary Module Resources