Skip to content
Spring Boot sb config 4 min read

@ConfigurationProperties

@ConfigurationProperties binds an entire tree of related properties to a typed Java object. Instead of scattering @Value placeholders across many classes, you get one type-safe, validated, IDE-completable holder for a group of settings — the recommended approach for any non-trivial configuration in Spring Boot.

Why type-safe binding

A single @Value is fine for one property, but groups of related keys belong together. Typed binding gives you compile-time field names, automatic type conversion, relaxed binding, startup validation, and a single source of truth.

# application.yml
app:
  mail:
    host: smtp.example.com
    port: 587
    from: [email protected]
    enabled: true
    timeout: 5s            # bound to a Duration

Binding to a class

Add @ConfigurationProperties with the shared prefix. Spring binds each nested key to a matching field via its setter.

@ConfigurationProperties(prefix = "app.mail")
public class MailProperties {

    private String host;
    private int port;
    private String from;
    private boolean enabled;
    private Duration timeout = Duration.ofSeconds(3);  // default

    // getters and setters
}

You must register the class so Spring creates and binds it (see registration below). Then inject it like any bean:

@Service
public class MailService {

    private final MailProperties props;

    public MailService(MailProperties props) {
        this.props = props;
    }

    public void send(String to, String body) {
        // props.getHost(), props.getPort(), ...
    }
}

Binding to a record

For immutable configuration, bind to a Java record. Constructor binding works automatically — no setters needed. See Java Records for the language feature.

@ConfigurationProperties(prefix = "app.mail")
public record MailProperties(
        String host,
        int port,
        String from,
        boolean enabled,
        @DefaultValue("3s") Duration timeout) {
}

@DefaultValue supplies a fallback for a record component when the property is absent. Records are the cleanest option when settings never change after startup.

Registering the properties

There are three ways to make Spring instantiate and bind the class.

// 1. @EnableConfigurationProperties on a config class — explicit
@Configuration
@EnableConfigurationProperties(MailProperties.class)
public class AppConfig { }
// 2. @ConfigurationPropertiesScan on the main class — scans a package
@SpringBootApplication
@ConfigurationPropertiesScan
public class Application { }
// 3. Treat the holder itself as a bean (works for setter-based classes)
@Component
@ConfigurationProperties(prefix = "app.mail")
public class MailProperties { /* ... */ }

Tip: Use @ConfigurationPropertiesScan once on your main class, then keep each properties type a plain annotated class or record. It scales better than listing every type in @EnableConfigurationProperties.

Nested objects

Properties nest naturally. A nested static class (or record component) maps to a sub-tree.

app:
  mail:
    host: smtp.example.com
    retry:
      max-attempts: 3
      backoff: 500ms
@ConfigurationProperties(prefix = "app.mail")
public class MailProperties {

    private String host;
    private final Retry retry = new Retry();  // initialized for setter binding

    public Retry getRetry() { return retry; }
    public String getHost() { return host; }
    public void setHost(String host) { this.host = host; }

    public static class Retry {
        private int maxAttempts;
        private Duration backoff;
        // getters and setters
    }
}

Lists and maps

Collections bind directly — no SpEL string-splitting required.

app:
  servers:
    - alpha.example.com
    - beta.example.com
  limits:
    free: 10
    pro: 100
    enterprise: 1000
@ConfigurationProperties(prefix = "app")
public class AppProperties {

    private List<String> servers = new ArrayList<>();
    private Map<String, Integer> limits = new HashMap<>();

    // getters and setters
}

A list of objects works too:

app:
  endpoints:
    - name: orders
      url: https://orders.internal
    - name: billing
      url: https://billing.internal
private List<Endpoint> endpoints = new ArrayList<>();

public record Endpoint(String name, String url) { }

Relaxed binding

Spring matches property keys to fields loosely, so the canonical kebab-case in YAML maps to camelCase fields, and environment variables map to either.

Property sourceFormBinds to
YAML / .propertiesapp.mail.max-attemptsmaxAttempts
Properties (camelCase)app.mail.maxAttemptsmaxAttempts
Environment variableAPP_MAIL_MAX_ATTEMPTSmaxAttempts
System propertyapp.mail.maxAttemptsmaxAttempts

Note: Environment-variable binding is only available for @ConfigurationProperties, which is one more reason to prefer it for containerized deployments. See Externalized Configuration.

Validation with @Validated

Add @Validated and jakarta-validation constraints to fail fast at startup when configuration is wrong. Requires spring-boot-starter-validation.

@ConfigurationProperties(prefix = "app.mail")
@Validated
public class MailProperties {

    @NotBlank
    private String host;

    @Min(1) @Max(65535)
    private int port;

    @Email
    private String from;

    @NotNull
    private Duration timeout;

    // getters and setters
}

If app.mail.host is missing, the context fails immediately:

Output:

***************************
APPLICATION FAILED TO START
***************************

Binding to target ... MailProperties failed:

    Property: app.mail.host
    Reason: must not be blank

This is far better than discovering a misconfiguration when the first email send fails in production.

IDE metadata

Add the annotation processor so your IDE offers autocompletion and docs for your custom keys in application.yml.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

Best Practices

  • Prefer @ConfigurationProperties over multiple @Value annotations for any group of related settings.
  • Use records (with @DefaultValue) for immutable config; use setter-based classes when you need post-construction adjustment.
  • Register once with @ConfigurationPropertiesScan on the main application class.
  • Validate with @Validated plus jakarta constraints to fail fast on bad configuration.
  • Initialize collection and nested fields (= new ArrayList<>()) so setter binding has something to populate.
  • Include spring-boot-configuration-processor for IDE autocompletion of your keys.
Last updated June 13, 2026
Was this helpful?