How do you make Spring Boot validate an @ConfigurationProperties class at startup with JSR-380 constraints?
answer
- @Validated (Spring) not @Valid
- JSR-380 constraints on fields
- starter-validation = Hibernate Validator
- no provider = silently skipped
- @EnableConfigurationProperties / scan
basics
~10 sPut Spring's @Validated on the @ConfigurationProperties class, add constraint annotations like @NotNull or @Min on the fields, and include spring-boot-starter-validation so a Bean Validation provider (Hibernate Validator) is on the classpath.
solid answer
~40 sAnnotate the @ConfigurationProperties class with Spring's @Validated (org.springframework.validation.annotation.Validated), then add Jakarta Bean Validation (JSR-380) constraints such as @NotNull, @NotBlank, @Min, @Max, @Email on the bound fields or constructor parameters. You must also have a validation provider on the classpath — normally spring-boot-starter-validation, which pulls in Hibernate Validator. Register the properties bean via @EnableConfigurationProperties or @ConfigurationPropertiesScan. When Spring binds external config (application.yml, env vars) to the object, it runs the constraints; any violation fails the binding and the application context refuses to start. This gives fail-fast configuration: a bad or missing property is caught at boot instead of causing a NullPointerException hours later at runtime.
code
java · 25 linesimport jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties(prefix = "app.mail")
@Validated // Spring's annotation switches on JSR-380 validation of the bound result
public class MailProperties {
@NotBlank // must be present and non-blank
private String host;
@Min(1) @Max(65535) // valid TCP port range
private int port = 25;
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; }
}
// Enable it:
// @EnableConfigurationProperties(MailProperties.class)
// or @ConfigurationPropertiesScan on the application classgo deeper
Know the trio: @Validated + JSR-380 constraints + starter-validation, and that failures stop startup.
Explain @Validated vs @Valid and the silent-skip gotcha when no provider is present.
Add how binding+validation is wired (ConfigurationPropertiesBindingPostProcessor, JSR303 validator) and constructor binding support.
Frame fail-fast config validation as a reliability practice and decide which invariants belong here vs. runtime health checks.
## What this is `@ConfigurationProperties` is Spring Boot's mechanism for binding external configuration (from `application.yml`/`application.properties`, environment variables, command-line args, etc.) onto a typed Java/Kotlin object. **Validating bound config** means checking those values *at startup* so misconfiguration fails the boot instead of surfacing as a mysterious runtime error later. ## The three required ingredients 1. **`@Validated` on the class** — this is Spring's own annotation `org.springframework.validation.annotation.Validated`, NOT `jakarta.validation.Valid`. Only `@Validated` on the `@ConfigurationProperties` type switches on validation of the bound result. Putting `@Valid` on the class does nothing here. 2. **JSR-380 (Jakarta Bean Validation) constraint annotations** on the fields/parameters, from `jakarta.validation.constraints.*`: `@NotNull`, `@NotBlank`, `@NotEmpty`, `@Min`, `@Max`, `@Positive`, `@Size`, `@Email`, `@Pattern`, etc. 3. **A Bean Validation provider on the classpath** — usually **Hibernate Validator**, brought in by `spring-boot-starter-validation`. This is the single most common gotcha: `@Validated` + constraints with *no provider* means validation is **silently skipped** — no error, no warning. ## Registering the properties bean The class must become a Spring bean via `@EnableConfigurationProperties(MailProperties.class)` on a config class, or `@ConfigurationPropertiesScan` on the application, or by annotating it as a `@Component`. ## How validation is wired internally Boot's `ConfigurationPropertiesBindingPostProcessor` performs binding; when the target class carries `@Validated`, a `ConfigurationPropertiesJsr303Validator` runs the JSR-380 constraints against the freshly bound object during binding. Because it happens at context startup, a failure aborts the whole application. ## Constructor binding Validation works with immutable/constructor-bound properties (records or classes using constructor binding). Constraints go on the constructor parameters / record components, e.g. `record MailProperties(@NotNull String host, @Min(1) int port) {}`. ## When to use Always, for any property that is required or has a valid range (ports, timeouts, URLs, credentials). It converts a class of latent production bugs into an immediate, well-labelled startup failure.
- Why doesn't putting jakarta.validation.@Valid on the class trigger validation of the properties?Boot only activates JSR-303/380 validation of a bound @ConfigurationProperties object when the class carries Spring's @Validated. @Valid is a cascade marker for nested objects, not the switch that turns validation on for the top-level type.
- You added @Validated and @NotNull but bad config still boots fine. Why?No Bean Validation provider is on the classpath. Without Hibernate Validator (spring-boot-starter-validation), Spring has nothing to run the constraints and skips validation silently.
saying these in an interview costs you the question
- Thinking @Valid on the class enables validation instead of @Validated
- Assuming validation works without a provider like Hibernate Validator on the classpath
- Believing @ConfigurationProperties validates automatically without any annotation