Spring Boot Configuration Properties: @ConfigurationProperties and Relaxed Binding

Master Spring Boot external configuration with @ConfigurationProperties, relaxed binding rules, and application.yml/yml setup.

published: reading time: 13 min read author: GeekWorkBench
Quick Summary

Master Spring Boot external configuration with @ConfigurationProperties, relaxed binding rules, and application.yml/yml setup. The guide uses practical examples to explain when to use @configurationproperties vs @value, defining @configurationproperties classes and shows how to apply the ideas in a Spring Boot project. It closes with common pitfalls and production checks so you can apply the pattern with fewer surprises.

Spring Boot Configuration Properties

Introduction

Spring Boot’s externalized configuration system is one of its most powerful features. It lets you decouple your application code from environment-specific values, so the same JAR runs everywhere—from a developer’s laptop to production Kubernetes clusters.

Instead of hardcoding database URLs, feature flags, or third-party API keys, you inject them. Spring Boot’s configuration binding connects application.yml properties directly to typed Java objects through @ConfigurationProperties. The framework handles the plumbing, the conversion, and the relaxed binding between kebab-case YAML keys and camelCase Java fields.

This post walks through the two main approaches, how relaxed binding works under the hood, the pitfalls that trip most teams, and what a production-ready configuration setup actually looks like.

When to Use @ConfigurationProperties vs @Value

Spring gives you two primary ways to inject configuration: @ConfigurationProperties and @Value. They serve different purposes.

Use @ConfigurationProperties when you need:

  • Multiple related configuration values grouped into a typed object
  • Validation with JSR-380 (@NotNull, @Min, @Max, etc.)
  • Default values without ceremony
  • Spring Boot’s relaxed binding between YAML/environment variables and Java fields
  • IDE autocomplete on configuration keys via metadata generation

Use @Value when you need:

  • A single, isolated value injected into a field
  • SpEL expressions (#{someBean.method()})
  • Quick prototyping with no intention to expand

The performance difference is negligible—both resolve at startup. The maintenance difference is not. A class with 15 @Value annotations is harder to read and test than a single @ConfigurationProperties class with 15 fields.

Defining @ConfigurationProperties Classes

Annotate a class with @ConfigurationProperties and give it a prefix. Spring Boot will bind any matching properties to its fields.

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "app.mailing")
public class MailingProperties {

    private String host = "localhost";
    private int port = 587;
    private @DefaultValue("noreply@example.com") String fromAddress;
    private boolean enabled = true;

    // getters and setters (required for binding)
    public String getHost() { return host; }
    public void setHost(String host) { this.host = host; }
    public int getPort() { return port; }
    public void setPort(int port) { this.port = port; }
    public String getFromAddress() { return fromAddress; }
    public void setFromAddress(String fromAddress) { this.fromAddress = fromAddress; }
    public boolean isEnabled() { return enabled; }
    public void setEnabled(boolean enabled) { this.enabled = enabled; }
}

You can also use @EnableConfigurationProperties on a @Configuration class instead of @Component—useful when you want to keep configuration binding scoped to a specific module:

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(MailingProperties.class)
public class MailingConfiguration {
    // MailingProperties is now a bean in this configuration context
}

application.yml / application.yaml Structure

Spring Boot automatically loads application.yml (or application.yaml) from several locations, in order of precedence (highest wins):

graph TD
    A["Command-line args<br/>--spring.config.location"] --> B["OS environment variables<br/>SPRING_APPLICATION_JSON"]
    B --> C["java:comp/env JNDI"]
    C --> D["Application JAR<br/>application.yml"]
    D --> E["Profile-specific<br/>application-{profile}.yml"]
    E --> F["Application JAR<br/>application-{profile}.yml"]
    F --> G["Locations from<br/>spring.config.additional-location"]
    G --> H["Default fallback<br/>classpath application.yml"]

Properties can also be split across profiles. If you activate the prod profile, Spring merges application.yml with application-prod.yml, with profile-specific values overriding the base file.

Example application.yml

spring:
  application:
    name: user-service
  datasource:
    url: jdbc:postgresql://localhost:5432/users
    username: ${DB_USER:appuser}
    password: ${DB_PASSWORD}
    driver-class-name: org.postgresql.Driver
    hikari:
      maximum-pool-size: 20
      connection-timeout: 30000

server:
  port: ${APP_PORT:8080}
  servlet:
    context-path: /api

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  endpoint:
    health:
      show-details: when-authorized

app:
  mailing:
    host: smtp.example.com
    port: 587
    from-address: noreply@example.com
    enabled: true
  feature-flags:
    new-checkout-flow: ${FF_NEW_CHECKOUT:false}
    beta-search: ${FF_BETA_SEARCH:false}

Property Placeholders and Default Values

Use ${VAR_NAME:default} syntax for environment variable substitution with fallbacks. The syntax is property: ${ENV_VAR:fallback}.

The colon-separated default works at every level—datasource URL, server port, feature flags, anything. This makes it easy to externalize secrets to environment variables while providing safe development defaults.

Relaxed Binding

Spring Boot’s relaxed binding handles multiple naming conventions automatically, which is useful when your YAML comes from different sources.

YAML / Environment Format Java Field (camelCase)
app.mailing.host app.mailing.host
app.mailing.host-name app.mailing.hostName
APP_MAILING_HOST app.mailing.host
app.mailing.HOST app.mailing.host (case-insensitive)

This means all of these are equivalent:

# YAML - kebab-case
app:
  mailing:
    from-address: noreply@example.com
# properties file - kebab-case
app.mailing.from-address=noreply@example.com
# Environment variable - SCREAMING_SNAKE_CASE
APP_MAILING_FROM_ADDRESS=noreply@example.com

Spring Boot normalizes everything when binding. You can confidently use whichever format fits your team’s conventions or platform constraints. Kubernetes ConfigMap keys are typically kebab-case, so APP_MAILING_FROM_ADDRESS maps cleanly to app.mailing.from-address.

@Name and @DefaultValue Annotations

When your YAML key doesn’t match the Java field name, use @Name to explicit map:

import org.springframework.boot.context.properties.bind.Name;
import org.springframework.boot.context.properties.bind.DefaultValue;

@ConfigurationProperties(prefix = "app.mailing")
public class MailingProperties {

    @Name("from-email")
    @DefaultValue("noreply@example.com")
    private String fromAddress;
}

This resolves the mismatch between from-email in YAML and fromAddress in Java without renaming either.

Validation and Type Conversion

Spring Boot automatically converts YAML values to the target field type. String, int, long, boolean, Duration, InetAddress, and many others work out of the box. For custom types, you can register a Converter bean.

JSR-380 validation adds a layer of safety:

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;

@ConfigurationProperties(prefix = "app.mailing")
@Validated
public class MailingProperties {

    @NotBlank
    private String host;

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

    @Email
    private String fromAddress;
}

When validation fails, Spring Boot prevents the application from starting and logs a clear BindValidationException. This catches misconfiguration at startup rather than at the first request.

Configuration Properties in Practice

Injecting into a Service

import org.springframework.stereotype.Service;

@Service
public class NotificationService {

    private final MailingProperties mailing;

    public NotificationService(MailingProperties mailing) {
        this.mailing = mailing;  // constructor injection for testability
    }

    public void sendWelcomeEmail(String to) {
        if (!mailing.isEnabled()) {
            return;
        }
        // use mailing.getHost(), mailing.getPort(), etc.
    }
}

Constructor injection (instead of field injection) makes the service unit-testable—you can mock MailingProperties without Spring’s reflection-based injection.

Multi-Environment Configuration

# application.yml (base)
app:
  mailing:
    host: localhost
    port: 587
    enabled: false # off by default

---
# application-dev.yml (profile-specific overrides)
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/devdb

app:
  mailing:
    enabled: true
    from-address: dev@example.com

Activate with –spring.profiles.active=dev or SPRING_PROFILES_ACTIVE=dev. Profile-specific files deep-merge with the base—only the fields that differ need to appear in the profile file.

Production Failure Scenarios

These are the errors I see most often when configuration binding goes wrong in production.

BindingValidationException at startup

  • Cause: A YAML property fails JSR-380 validation. Check the startup logs for the specific field and constraint violation.
  • Fix: Either fix the YAML value, adjust the validation constraint, or remove @Validated if you need looser behavior during migration.

A property that should be bound but isn’t

  • Cause: The YAML key doesn’t match the @ConfigurationProperties prefix. Spring Boot doesn’t warn you about unused keys—it binds what it finds and ignores the rest silently.
  • Fix: Enable DEBUG logging (logging.level.org.springframework.boot.context.properties=DEBUG) to see which properties are being bound.

NoSuchBeanException for a @ConfigurationProperties class

  • Cause: The class isn’t registered as a Spring bean. It needs either @Component on the class or @EnableConfigurationProperties(MyProperties.class) on a @Configuration class.
  • Fix: Add one of those two annotations.

Environment variables that seem to have no effect

  • Cause: Double underscores in environment variable names (APPMAILINGHOST) get parsed as kebab-case separators, not literal underscores. Single underscores work correctly. Typos also happen.
  • Fix: Check the variable name. APP_MAILING_HOST maps to app.mailing.host. APPMAILINGHOST does not.

Observability Checklist

  • Confirm all @ConfigurationProperties classes are registered as beans (via @Component or @EnableConfigurationProperties)
  • Add JSR-380 validation to catch misconfiguration at startup
  • Enable DEBUG logging for property binding during development: logging.level.org.springframework.boot.context.properties=DEBUG
  • Verify spring-boot-configuration-processor is added to generate IDE metadata
  • Protect /env and /configprops actuator endpoints with authentication
  • Document all custom property prefixes in a README or architecture doc
  • Ensure all secrets are injected via environment variables, not hardcoded defaults
  • Use ApplicationContextRunner to unit test property binding for critical configuration classes

Quick Recap Checklist

  • @ConfigurationProperties provides type-safe, validated, relaxed-binding configuration
  • Use @Component or @EnableConfigurationProperties to register the binding class as a bean
  • YAML keys in kebab-case (from-address) map to camelCase Java fields (fromAddress)
  • Environment variables use SCREAMING_SNAKE_CASE with underscores as separators
  • Use @Validated + JSR-380 annotations for startup-time validation
  • Use @Name when YAML key and Java field name cannot be aligned via conventions
  • Use @DefaultValue for safe fallbacks, but never for secrets
  • Constructor injection of @ConfigurationProperties makes services unit-testable
  • Profile-specific YAML files deep-merge with the base application.yml
  • ApplicationContextRunner with EnableConfigurationProperties is the standard test pattern

Trade-off Table

Approach Pros Cons
@ConfigurationProperties + class Type-safe, validatable, relaxed binding, IDE autocomplete Slight boilerplate for getters/setters
@Value annotations Quick, no extra class needed No validation, poor testability, no relaxed binding
Raw Environment API Maximum flexibility Verbose, string-based, error-prone
Profile-specific YAML Clean environment separation Can lead to configuration duplication across profiles

Implementation Snippets

Generating Configuration Metadata

Add the annotation processor to pom.xml or build.gradle to get IDE autocomplete:

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

With this processor active, running a build generates META-INF/spring-configuration-metadata.json. IDEs like IntelliJ IDEA use this file to provide autocomplete and documentation for your custom properties.

Testing Configuration Properties

import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;

class MailingPropertiesTest {

    private final ApplicationContextRunner runner = new ApplicationContextRunner()
        .withPropertyValues("app.mailing.host=smtp.test.com", "app.mailing.port=1025")
        .withConfiguration(EnableConfigurationProperties(MailingProperties.class));

    @Test
    void bindsPropertiesCorrectly() {
        runner.run(context -> {
            MailingProperties props = context.getBean(MailingProperties.class);
            assertThat(props.getHost()).isEqualTo("smtp.test.com");
            assertThat(props.getPort()).isEqualTo(1025);
        });
    }
}

ApplicationContextRunner with @EnableConfigurationProperties is the cleanest way to unit test property binding without a full Spring Boot application context.

Security Notes

  • Never commit secrets to application.yml in version control. Use environment variables (${DB_PASSWORD}) or a secrets manager (HashiCorp Vault, AWS Secrets Manager, Kubernetes Secrets).
  • @DefaultValue on password fields: If you use @DefaultValue(“password”) on a password field and that YAML is committed, it becomes a security incident. Default values for secrets should be empty or throw an exception if not overridden.
  • Sensitive values in actuator /env endpoint: Spring Boot Actuator’s /env endpoint exposes configuration property values. Protect it with Spring Security or exclude sensitive property names via spring.security.enabled and endpoint.env.show-values configuration.

Common Pitfalls

  1. Forgetting getters and setters: Spring Boot’s binder uses reflection to call setters. Without them, binding silently does nothing for mutable fields.
  2. Mismatched property names with underscores: APP_MAILING_FROM_ADDRESS in an environment variable binds to app.mailing.from-address, not app.mailing.fromAddress. Understand the relaxed binding rules before debugging.
  3. Deep merging behavior: Profile-specific YAML does not replace entire parent objects—it deep-merges. If app.mailing exists in the base and you override only app.mailing.enabled in the profile, the rest of app.mailing is inherited. This is usually what you want, but can be surprising.
  4. Configuration properties not updating at runtime: @ConfigurationProperties binds once at startup. If you need dynamic updates without restart, consider Spring Cloud Config with a refresh scope or @RefreshScope.

Interview Questions

1. How does Spring Boot's relaxed binding work between YAML properties and Java fields?

Spring Boot's relaxed binding normalizes property names from multiple formats before matching them to @ConfigurationProperties fields. A YAML key like app.mailing.from-address can bind to a Java field fromAddress because Spring strips dashes and converts the remaining segments to camelCase. Environment variables follow a similar rule—APP_MAILING_FROM_ADDRESS maps to the same property because underscores act as separators. This means you can use whichever naming convention fits your deployment context (Kubernetes ConfigMaps use kebab-case, shell scripts use SCREAMING_SNAKE_CASE) without changing your Java code.

2. What is the difference between @ConfigurationProperties and @Value, and when would you prefer each?

@ConfigurationProperties binds a group of related properties to a typed Java object with validation support, relaxed binding, and IDE autocomplete via metadata generation. @Value injects individual literal values and supports SpEL expressions but offers none of those benefits. Use @ConfigurationProperties for structured, production configuration that may grow or need validation. Use @Value for quick prototyping, single isolated values, or when you need SpEL. The maintenance and testability advantages of @ConfigurationProperties make it the default choice for anything beyond throwaway code.

3. How do you ensure configuration properties are validated at startup and not silently accepted?

Add the @Validated annotation to your @ConfigurationProperties class and apply JSR-380 constraints like @NotBlank, @Min, @Max, and @Email to the fields. Spring Boot's configuration binding checks these constraints during the binding phase. If any constraint fails, the application halts with a BindValidationException and a clear error message listing which property violated which constraint. This catches misconfiguration before the app serves any traffic, which is far better than discovering a wrong timeout value at 3 AM.

4. What happens if a YAML property in application.yml does not have a matching field in the @ConfigurationProperties class?

Spring Boot silently ignores it. The binder only maps properties that have a corresponding field in the target class. There is no warning or error for extra properties, which can lead to typos in YAML sitting unnoticed for months. Enabling DEBUG logging for org.springframework.boot.context.properties makes the binding visible and helps identify orphaned properties. Alternatively, consider using a schema validator for your YAML files to catch unknown keys as part of your CI pipeline.

5. How do you test @ConfigurationProperties classes in isolation without loading the full Spring context?

Use ApplicationContextRunner with EnableConfigurationProperties to create a minimal context containing only the properties class you want to test. Call withPropertyValues() to inject specific YAML keys, then assert on the bound bean. This approach spins up in milliseconds compared to a full application context, gives precise control over which properties are set, and works well in JUnit 5 tests. It is the standard pattern for unit testing configuration binding without the overhead of a full Spring Boot application.

Further Reading

Conclusion

Spring Boot’s @ConfigurationProperties is the recommended way to externalize configuration in production applications. It gives you type-safe, validated, IDE-autocompleteable configuration that adapts across environments through relaxed binding — kebab-case YAML keys map to camelCase Java fields, and environment variables in SCREAMING_SNAKE_CASE connect seamlessly without code changes.

The key to using @ConfigurationProperties well is registration: add @Component or @EnableConfigurationProperties to make the binding class a Spring bean. Use @Validated to catch misconfiguration at startup rather than on the first request. Prefer constructor injection for testability. Profile-specific YAML files deep-merge with the base configuration, letting you override only what changes per environment.

The ApplicationContextRunner test pattern lets you verify binding behavior in milliseconds without loading the full application context, catching configuration errors in unit tests before they surface in production.

Category

Related Posts

Spring Boot Build Tools: Maven & Gradle

Configure Maven and Gradle for Spring Boot projects—plugins, dependency management, packaging JARs and WARs, and build automation essentials.

#spring-boot #spring-boot-roadmap #learning-path

Embedded Web Servers in Spring Boot: Tomcat, Jetty, Undertow

Configure embedded servers in Spring Boot: compare Tomcat, Jetty, and Undertow, tune thread pools, enable access logs, and switch implementations.

#spring-boot #spring-boot-roadmap #learning-path

JUnit 5 & Jupiter: Lifecycle, Nested & Parameterized Tests

Explore JUnit 5 Jupiter features: master test lifecycle annotations, organize tests with @Nested, and parameterize tests with @CsvSource and @MethodSource.

#spring-boot #spring-boot-roadmap #learning-path