skip to content

How is Bean Validation wired in a Spring context, and what configuration/lifecycle pitfalls should you know as a principal engineer?

level: principalimportance: nice to knowfreq 25%

answer

  1. ValidationAutoConfiguration: defaultValidator + MethodValidationPostProcessor
  2. setValidationMessageSource -> Spring i18n templates
  3. fail_fast property = stop at first violation
  4. validators stateless; interpolation needs EL factory
  5. two Validator types -> injection ambiguity

basics

~20 s

Spring Boot auto-registers a LocalValidatorFactoryBean (the injectable Validator) and a MethodValidationPostProcessor (for @Validated method validation). You can override them to customize the message source, constraint validator factory, or bootstrap options. Watch proxying, message interpolation, and validator statelessness.

solid answer

~40 s

In Spring Boot, ValidationAutoConfiguration registers two beans when a provider is present: a LocalValidatorFactoryBean (the 'defaultValidator' — a Jakarta Validator/ValidatorFactory plus Spring Validator) and a MethodValidationPostProcessor for @Validated method validation. To customize, define your own LocalValidatorFactoryBean and set its MessageInterpolator, ConstraintValidatorFactory, or messageSource so violation templates resolve through Spring's MessageSource. Principal-level concerns: (1) proxy semantics of method validation — self-invocation bypasses it, final classes/methods can't be proxied, and it adds an interceptor to every call. (2) Message interpolation — by default Hibernate Validator uses ValidationMessages.properties; wiring setValidationMessageSource lets you use Spring i18n. (3) Validators are shared and must be stateless/thread-safe. (4) Two Validator beans (Spring vs Jakarta types) can confuse injection; be explicit. (5) Bootstrapping cost and provider selection via META-INF/validation.xml. (6) Fail-fast mode can be enabled to stop at the first violation.

code

java · 26 lines
java
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.*;
import org.springframework.validation.beanvalidation.*;
import java.util.Map;

@Configuration
class ValidationConfig {

    // Override the default validator to use Spring MessageSource for i18n
    // and enable Hibernate Validator fail-fast.
    @Bean
    LocalValidatorFactoryBean defaultValidator(MessageSource messageSource) {
        var v = new LocalValidatorFactoryBean();
        v.setValidationMessageSource(messageSource); // {key} templates via Spring i18n
        v.getValidationPropertyMap().put("hibernate.validator.fail_fast", "true");
        return v; // SpringConstraintValidatorFactory kept by default -> DI in validators
    }

    // Ensure method validation uses THIS validator
    @Bean
    MethodValidationPostProcessor methodValidationPostProcessor(LocalValidatorFactoryBean v) {
        var p = new MethodValidationPostProcessor();
        p.setValidator(v);
        return p;
    }
}

go deeper

for a junior

Know Boot auto-configures the validator and method-validation post-processor; you rarely configure anything.

for a middle

Know how to override the LocalValidatorFactoryBean and wire a MessageSource for messages.

for a senior

Explain interpolation, fail-fast, statelessness, and injection ambiguity between Spring/Jakarta Validator types.

for a principal

Reason about interceptor ordering with @Transactional, provider selection via validation.xml, EL/interpolation deployment gotchas, and shared-bean implications across MVC and method validation.

**The auto-configuration.** Spring Boot's `ValidationAutoConfiguration` fires when a Bean Validation provider (Hibernate Validator) is on the classpath. It registers: - **`LocalValidatorFactoryBean`** named `defaultValidator` — the injectable validator (satisfies `jakarta.validation.Validator`, `jakarta.validation.ValidatorFactory`, and `org.springframework.validation.Validator`). Backed off if you define your own `Validator`/`ValidatorFactory`. - **`MethodValidationPostProcessor`** — enables `@Validated` method validation app-wide. Backed off if you declare your own. **Customizing the factory bean.** Subclass or configure `LocalValidatorFactoryBean`: - `setMessageInterpolator(...)` / `setValidationMessageSource(MessageSource)` — route constraint message templates (`{...}`) through Spring's `MessageSource` for i18n, instead of the default `ValidationMessages.properties` on the classpath. This unifies validation messages with the rest of your i18n. - `setConstraintValidatorFactory(...)` — normally the `SpringConstraintValidatorFactory` (enables DI into validators); override only for special lifecycle needs. - `setProviderClass(...)` / `getValidationPropertyMap()` — pin the provider or pass Hibernate Validator properties (e.g., **fail-fast** `hibernate.validator.fail_fast=true` to stop at the first violation instead of collecting all). - `META-INF/validation.xml` is the spec's XML descriptor for provider selection, default sequences, and constraint mappings; `LocalValidatorFactoryBean` honors it. **Message interpolation details.** A constraint `message()` may be a literal or a `{key}` template. Resolution order: Hibernate's default interpolator looks up `ValidationMessages.properties`; if you wire `setValidationMessageSource`, Spring's `MessageSource` (and locale) is consulted. Templates can reference constraint attributes like `{min}`, `{max}`, and use `${...}` EL expressions (EL support requires an expression factory on the classpath — a subtle deployment gotcha in trimmed runtimes). **Method-validation lifecycle and AOP.** `MethodValidationPostProcessor` proxies every `@Validated` bean and adds a `MethodValidationInterceptor` to the advice chain. Principal considerations: - **Ordering**: the interceptor participates in the AOP chain; interaction with `@Transactional`, retry, or security advice depends on advisor order. Validation should typically run **before** the transaction opens for parameter checks. - **Self-invocation and proxyability**: internal `this.` calls skip validation; `final` classes/methods and non-bean instances aren't validated. - **Overhead**: every method on a `@Validated` bean is intercepted; keep validators cheap. **Thread-safety.** `Validator` and `ValidatorFactory` are thread-safe and meant to be singletons. Custom `ConstraintValidator`s are potentially reused/shared — keep them **stateless**; capture immutable attributes in `initialize()` only. **Injection ambiguity.** Because `LocalValidatorFactoryBean` satisfies both the Spring and Jakarta `Validator` types, and app code may also define a Spring `Validator` for `WebDataBinder`, be explicit about which type you inject to avoid surprises. In MVC, the `defaultValidator` also backs `@Valid` body binding — a different subsystem sharing the same bean. **Spring 6.1+ note.** Spring Framework 6.1 / Boot 3.2 added built-in method validation for controllers and other components without needing the post-processor in some cases, and refined how method-validation violations map to responses. The classic, portable mechanism remains `LocalValidatorFactoryBean` + `MethodValidationPostProcessor`. **When to touch this.** Override the auto-config only for: i18n message sourcing, fail-fast, custom interpolation/EL, alternate providers, or specialized validator lifecycles. Otherwise rely on Boot's defaults.

  • How do you make constraint messages use Spring's i18n MessageSource?
    Configure a LocalValidatorFactoryBean and call setValidationMessageSource(messageSource). Then {key} message templates resolve through Spring's MessageSource with the current locale instead of the default classpath ValidationMessages.properties.
  • How do you make validation stop at the first violation instead of collecting all?
    Enable Hibernate Validator's fail-fast: set hibernate.validator.fail_fast=true via LocalValidatorFactoryBean.getValidationPropertyMap(). The validator then returns after the first constraint failure.
  • Where does validation sit relative to @Transactional in a @Validated @Transactional bean?
    Both are AOP interceptors; parameter validation should run before the transaction opens. Advisor ordering governs this — typically the validation interceptor precedes the transaction interceptor so invalid calls never start a transaction.

saying these in an interview costs you the question

  • Thinking you must configure the validator manually in Boot (it's auto-configured)
  • Assuming EL message expressions work without an expression factory on the classpath
  • Storing per-call state in a ConstraintValidator
  • Ignoring that self-invocation bypasses the method-validation proxy
  • Overlooking that the same defaultValidator backs MVC @Valid binding

context