Have you ever bound properties with @ConfigurationProperties, only to have the application crash at runtime because a value was empty or malformed? If the problem had been caught at startup, the outage could have been avoided entirely.
This article explains how to combine @ConfigurationProperties with Bean Validation to validate configuration values when the application starts. We will also look at how to automate the 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 the errors show up late. For example, if the database URL is an empty string, no error appears until the application actually tries to connect. It is not unusual to discover the mistake only after it causes an outage right after a production release.
The Fail Fast principle says that problems should surface as early as possible. If configuration values are validated at startup, a misconfiguration becomes apparent the moment you deploy. When it is caught in a development environment, the cost of fixing it is minimal.
Add 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 the dependency explicitly.
<!-- Maven -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
// Gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'
With this in place, Hibernate Validator is used as the implementation and the Bean Validation annotations become active.
Add @Validated to the @ConfigurationProperties Class
The key point is to annotate the configuration class with @Validated in addition to @ConfigurationProperties. Without @Validated, validation will not run even if you put constraint annotations on the fields.
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 changed from javax.validation to jakarta.validation. If you are on the 2.x line, import javax.validation.constraints.* instead.
To register the class, either annotate it with @Component or declare @EnableConfigurationProperties(AppProperties.class) in a configuration class. When distributing the class as a library, @EnableConfigurationProperties is recommended, because the class will not be picked up by component scanning.
Examples of Common Constraint Annotations
Let’s look at an example that combines the annotations you will use most often in practice.
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
@NotBlank
private String name;
// 正規表現でURLの形式チェック
@Pattern(regexp = "https?://.+", message = "有効なURLを指定してください")
private String endpointUrl;
// 1〜300秒の範囲チェック
@Min(1)
@Max(300)
private int timeoutSeconds;
// ゼロ以下を禁止
@Positive
private int maxConnections;
// getter/setter
}
@NotNull rejects only null, whereas @NotBlank also rejects empty strings and strings consisting solely of whitespace. A safe rule of thumb is to use @NotBlank for string fields and @NotNull for required non-string fields such as Integer or Boolean.
Propagating Validation to Nested Objects
It is common to group related settings, such as database configuration, into an inner class. In this case, be careful: if you forget @Valid, the constraints on the inner class are ignored. For the basics of @Valid, see How to Implement Validation Simply with the Spring Boot @Valid Annotation.
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
// @NotNull は null チェック、@Valid はネスト検証の伝播と役割が異なる
@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
If you omit app.database entirely or set pool-size to 0, the application fails at startup.
How to Read the Startup Error Message
When validation fails, a ConfigurationPropertiesBindException is thrown and the startup log prints output 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: tells you which field is the problem, Value: shows the value that was actually bound, Origin: points to where in application.yml the value was written, and Reason: describes the violated constraint. If multiple fields fail, the same block is repeated for each one. Property: is displayed as the field name in camelCase, but Origin: also shows the actual key name in kebab-case, which makes it easy to map back to application.yml.
Thanks to relaxed binding, the configuration file can use either camelCase or kebab-case and the values will still be bound. However, the official convention recommends kebab-case (timeout-seconds). It also matches the Origin: output, so you can locate the offending line faster.
Testing Configuration Validation with @SpringBootTest
Let’s make sure validation works by covering it with test code.
For the happy path, use @SpringBootTest. It starts the full application context and is well suited to verifying, as an integration test, that configuration values are bound correctly.
@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 有効な設定でコンテキストが起動する() {
assertThat(props.getName()).isEqualTo("MyApp");
assertThat(props.getTimeoutSeconds()).isEqualTo(30);
}
}
For failure cases, use ApplicationContextRunner. It does not start the entire context, so it is fast and well suited to validating a configuration properties class in isolation. A good split is to use @SpringBootTest for full-context integration checks and ApplicationContextRunner for lightweight, fast unit-level verification.
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 必須項目が未設定だと起動に失敗する() {
runner.withPropertyValues(
"app.name=", // 空文字はNG
"app.timeout-seconds=0" // 1未満はNG
)
.run(context ->
assertThat(context).hasFailed()
);
}
}
Checklist When Validation Does Not Run or Startup Does Not Fail
“I added the constraint annotations, but the application does not fail at startup” is a common question. The cause almost always falls into one of the following.
@Validatedis missing from the class: Even if you write@NotBlankand similar annotations on the fields, Spring will not trigger validation unless the class is annotated with@Validated.@Validis missing on the nested target: Validation of fields in an inner class does not propagate unless the parent field is annotated with@Valid. Remember that@NotNulland@Validserve different purposes.javax.validationandjakarta.validationare mixed: Spring Boot 3.x reacts only tojakarta.validation.constraints.*. Ifjavaxslips in through copy-pasting from older code, the annotations are present but ignored.spring-boot-starter-validationdependency is missing: Since 2.3, it is not included inspring-boot-starter-web. Check withmvn dependency:treethat the artifact is actually present.- The
@ConfigurationPropertiesclass is not registered as a Bean: Without either@Componentor@EnableConfigurationProperties, the values are never bound in the first place, so validation never runs.
If none of these helps, the quickest route is to grep the startup log for ConfigurationPropertiesBindException. If the exception is absent, that is a sign validation itself is not running. If it is present, follow the “How to Read the Startup Error Message” section of this article and interpret Property: and Reason:.
Notes on Spring Boot 2.x vs. 3.x
The syntax for constructor binding differs between versions.
In the 2.x style, you annotate the class with @ConstructorBinding.
// 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;
}
}
In the 3.x style, @ConstructorBinding can be omitted if there is only one constructor. If there are multiple constructors, put the annotation directly on the constructor you want to bind. Record classes can also be annotated with @ConfigurationProperties and @Validated as-is. Constructor binding is applied automatically for records, so @ConstructorBinding is unnecessary.
// Spring Boot 3.x(コンストラクタが1つなら @ConstructorBinding 不要)
@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;
}
}
Also, 3.x uses the jakarta.validation package. When migrating from 2.x, don’t forget to bulk-replace the import statements.
Summary
The steps for adding validation to @ConfigurationProperties are simple.
- Add
spring-boot-starter-validationas a dependency - Annotate the
@ConfigurationPropertiesclass with@Validated - Put constraint annotations such as
@NotBlankand@Patternon each field - Annotate nested objects with
@Validto propagate validation
That alone lets you detect configuration mistakes immediately at application startup. If you also write tests using ApplicationContextRunner, you can continuously guarantee the quality of your configuration validation in CI.
Note that this article covered validation of configuration values at startup. If you want to format and return validation errors for HTTP requests, see Production-Ready GlobalExceptionHandler Implementation and How to Create Custom Validation Annotations.
If you switch configuration values by profile, see also Safely Switching Environment-Specific Configuration with Spring Boot Profiles. If you are interested in encrypting configuration values, check out Encrypting Configuration Values with Jasypt.