Have you ever bound properties with @ConfigurationProperties, only to have the application crash at runtime because a value was empty or in an invalid format? It would be much better to catch these issues at startup and prevent failures before they happen.
This article walks through how to combine @ConfigurationProperties with Bean Validation to validate configuration values at application startup. We’ll also cover how to automate validation logic with test code.
For the basics of @ConfigurationProperties, see Spring Boot Properties Configuration Guide.
Why Validate at Startup?
The tricky thing about configuration problems is that errors surface late. For example, if a database URL is an empty string, no error appears until the application actually tries to connect. It’s not uncommon to discover a misconfiguration only after a production release causes an outage.
The Fail Fast principle says problems should be surfaced as early as possible. Validating configuration at startup means misconfigurations are caught the moment the application is deployed. If caught in a development environment, the cost to fix is minimal.
Adding spring-boot-starter-validation
Since Spring Boot 2.3, spring-boot-starter-validation is no longer included in spring-boot-starter-web. You need to add it as an explicit dependency.
<!-- Maven -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
// Gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'
This brings in Hibernate Validator as the implementation and enables Bean Validation annotations.
Adding @Validated to @ConfigurationProperties
The key is to add @Validated to your configuration class alongside @ConfigurationProperties. Without @Validated, constraint annotations on fields will not be enforced.
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
@NotBlank
private String name;
@NotNull
private Integer timeoutSeconds;
// getter/setter
}
In Spring Boot 3.x, the package has changed from javax.validation to jakarta.validation. If you are on 2.x, import from javax.validation.constraints.* instead.
To register the class, either annotate it with @Component or declare @EnableConfigurationProperties(AppProperties.class) in a configuration class. For library distributions, @EnableConfigurationProperties is recommended since it does not rely on component scanning.
Applying Common Constraint Annotations
Here is an example combining commonly used annotations.
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
@NotBlank
private String name;
// Validate URL format with a regular expression
@Pattern(regexp = "https?://.+", message = "Please specify a valid URL")
private String endpointUrl;
// Enforce a range of 1–300 seconds
@Min(1)
@Max(300)
private int timeoutSeconds;
// Reject zero or negative values
@Positive
private int maxConnections;
// getter/setter
}
@NotNull rejects only null, while @NotBlank also rejects empty strings and strings consisting only of whitespace. Using @NotBlank for string fields is generally the safer choice.
Propagating Validation to Nested Objects
It is common to group related settings, such as database configuration, into inner classes. In this case, forgetting @Valid causes the constraints on the inner class to be silently ignored. For more on the basics of @Valid, see How to Implement Validation Simply with Spring Boot’s @Valid Annotation.
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
// @NotNull checks for null; @Valid propagates validation into the nested object — they serve different roles
@NotNull
@Valid
private Database database;
public static class Database {
@NotBlank
private String url;
@Min(1)
@Max(100)
private int poolSize;
// getter/setter
}
// getter/setter
}
The corresponding application.yml looks like this:
app:
database:
url: jdbc:postgresql://localhost:5432/mydb
pool-size: 10
Omitting the entire app.database section or setting pool-size to 0 will cause a startup error.
Reading Startup Error Messages
When validation fails, a ConfigurationPropertiesBindException is thrown and the startup log outputs an entry in the following format:
APPLICATION FAILED TO START
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException:
Failed to bind properties under 'app' to com.example.AppProperties failed:
Property: app.timeoutSeconds
Value: "0"
Origin: "app.timeout-seconds" from property source "application.yml" - 5:20
Reason: must be greater than or equal to 1
Action:
Update your application's configuration
Property: identifies which field caused the problem, Value: shows the actual bound value, Origin: shows where in application.yml the value came from, and Reason: describes the violated constraint. When multiple fields fail, the same block is repeated for each. Note that Property: displays the field name in camelCase, while Origin: includes the actual kebab-case key name, making it easier to locate the entry in application.yml.
Testing Configuration Validation with @SpringBootTest
Make sure your validation logic is covered by tests.
For the happy path, use @SpringBootTest. It starts the full application context and is well suited for integration-level verification that configuration values are correctly bound.
@SpringBootTest
@TestPropertySource(properties = {
"app.name=MyApp",
"app.endpoint-url=https://api.example.com",
"app.timeout-seconds=30",
"app.max-connections=5"
})
class AppPropertiesValidTest {
@Autowired
private AppProperties props;
@Test
void contextStartsWithValidConfiguration() {
assertThat(props.getName()).isEqualTo("MyApp");
assertThat(props.getTimeoutSeconds()).isEqualTo(30);
}
}
For failure cases, use ApplicationContextRunner. It does not start the full context, making it fast and well suited for unit-level validation testing of configuration properties classes. A good rule of thumb: use @SpringBootTest for full-context integration verification, and ApplicationContextRunner for lightweight, fast unit validation.
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
class AppPropertiesInvalidTest {
private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withUserConfiguration(TestConfig.class);
@EnableConfigurationProperties(AppProperties.class)
static class TestConfig {}
@Test
void startupFailsWhenRequiredFieldsAreMissing() {
runner.withPropertyValues(
"app.name=", // empty string is invalid
"app.timeout-seconds=0" // less than 1 is invalid
)
.run(context ->
assertThat(context).hasFailed()
);
}
}
Notes on Spring Boot 2.x vs 3.x
The syntax for constructor binding differs between versions.
2.x style applies @ConstructorBinding at the class level.
// Spring Boot 2.x
@ConfigurationProperties(prefix = "app")
@ConstructorBinding
public class AppProperties {
private final String name;
private final int timeoutSeconds;
public AppProperties(String name, int timeoutSeconds) {
this.name = name;
this.timeoutSeconds = timeoutSeconds;
}
}
3.x style allows @ConstructorBinding to be omitted when there is only one constructor. If there are multiple constructors, apply it directly to the one you want to bind.
// Spring Boot 3.x (@ConstructorBinding not required with a single constructor)
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private final String name;
private final int timeoutSeconds;
public AppProperties(String name, int timeoutSeconds) {
this.name = name;
this.timeoutSeconds = timeoutSeconds;
}
}
Additionally, 3.x uses the jakarta.validation package. When migrating from 2.x, remember to do a bulk replacement of the import statements.
Summary
Adding validation to @ConfigurationProperties is straightforward:
- Add
spring-boot-starter-validationas a dependency - Annotate the
@ConfigurationPropertiesclass with@Validated - Add constraint annotations such as
@NotBlankand@Patternto each field - Add
@Validto nested objects to propagate validation
With just these steps, configuration mistakes are caught immediately at application startup. Writing tests with ApplicationContextRunner lets you continuously verify configuration validation quality in CI.
If you switch configuration values by profile, see Safely Switching Environment-Specific Configuration with Spring Boot Profiles. If you are interested in encrypting configuration values, check out Encrypting Configuration Values with Jasypt.