How does @Validated enable method-level validation on Spring beans, and what makes it work?
answer
- @Validated on the TYPE, not the method
- MethodValidationPostProcessor -> AOP proxy
- throws ConstraintViolationException (jakarta), not MethodArgumentNotValid
- self-invocation bypasses it
- @Valid param = cascade into graph
basics
~20 sPut @Validated on a bean's class; then constraint annotations on its method parameters (like @NotNull) and return values are enforced on every call. A MethodValidationPostProcessor creates an AOP proxy that checks them and throws ConstraintViolationException on failure.
solid answer
~40 s@Validated (org.springframework.validation.annotation.Validated) at the class level marks a Spring bean for method validation. A MethodValidationPostProcessor bean-post-processor detects it and wraps the bean in an AOP proxy backed by a MethodValidationInterceptor. On each method call the interceptor validates parameters annotated with constraints (@NotNull, @Size, etc.) and, if the return value is annotated, validates that too; cascade with @Valid on a parameter to validate nested objects. On failure it throws jakarta.validation.ConstraintViolationException carrying the violations — this happens before/after the method body respectively. Spring Boot auto-configures MethodValidationPostProcessor. Because it is proxy-based, the usual AOP caveats apply: only external calls through the proxy are intercepted (self-invocation is not), the bean must be a Spring-managed bean, and the target should be proxyable. This is distinct from @Valid request-body binding in MVC.
code
java · 19 linesimport org.springframework.validation.annotation.Validated;
import org.springframework.stereotype.Service;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
@Service
@Validated // <-- class level: enables method validation via the post-processor's proxy
class PricingService {
// param constraints enforced on every external call
@NotNull
public Money quote(@NotBlank String sku, @Positive int qty) {
return Money.of(qty * 100);
}
// @Valid cascades into the nested object's own constraints
public void submit(@Valid Order order) { /* ... */ }
}
// Calling quote("", 0) throws jakarta.validation.ConstraintViolationException BEFORE the body runs.go deeper
Know that @Validated on the class plus constraint annotations on parameters enforces them on calls.
Explain the two parts (annotation + MethodValidationPostProcessor), the proxy, and that it throws ConstraintViolationException.
Discuss AOP proxy caveats (self-invocation, proxyability), return-value validation, and @Validated vs @Valid roles.
Contrast with Spring 6.1 built-in controller method validation, discuss where to place the boundary, and exception-translation strategy across the app.
**The problem it solves:** Field-level constraints only fire when something calls `validator.validate(obj)`. **Method validation** (part of Bean Validation 1.1+/Jakarta Validation) lets you enforce constraints on **method parameters and return values** automatically whenever the method is invoked — so a service method can declare `save(@NotNull @Valid Order o)` and callers can't sneak past. **Two moving parts:** 1. **`@org.springframework.validation.annotation.Validated`** placed on the **class** (type level) marks the bean as a candidate for method validation. (Note: this is Spring's `@Validated`, not `jakarta.validation.Valid`. `@Validated` also carries the validation-**groups** value used to select groups.) 2. **`MethodValidationPostProcessor`** — a `BeanPostProcessor` that scans beans, and for any annotated with `@Validated` (by default) creates an **AOP proxy** whose advice is a `MethodValidationInterceptor`. That interceptor delegates to the Jakarta `ExecutableValidator` (`validator.forExecutables()`). **What gets validated:** Constraint annotations directly on parameters (`@NotNull String name`, `@Size(max=10) String code`), and `@Valid` on a parameter to **cascade** into the object graph. Constraints on the **return value** (annotation on the method itself, e.g. `@NotNull public Order find(...)`) validate the result after the body runs. Cross-parameter constraints are also supported. **Failure behavior:** A violation throws **`jakarta.validation.ConstraintViolationException`** (note: NOT Spring's `MethodArgumentNotValidException`, which is the MVC binding exception). The exception's `getConstraintViolations()` returns the set. Parameter validation happens **before** the method body executes; return-value validation **after**. **Spring Boot:** `ValidationAutoConfiguration` registers a `MethodValidationPostProcessor` automatically when a provider is on the classpath, so usually you only add `@Validated` to your class. As of Spring Framework 6.1 / Boot 3.2 there's also built-in method validation on controllers without a post-processor, but the classic mechanism here is the post-processor + `@Validated`. **AOP gotchas (critical):** - **Proxy-based**, so only calls that go **through the proxy** are intercepted. **Self-invocation** (`this.method(...)` from within the same bean) bypasses validation. - The class must be a **Spring-managed bean**; `new`-ing it yourself gives no validation. - With JDK dynamic proxies the bean needs an interface, or set `proxyTargetClass`; Boot defaults to CGLIB class proxies so this is usually fine, but `final` classes/methods can't be proxied. - Putting `@Validated` on the **method** does nothing for triggering — it belongs on the **type** (its method-level use is only for selecting groups on MVC `@Valid` binding, a different feature). **@Validated vs @Valid:** `@Validated` triggers/configures method validation and selects groups; `@Valid` (Jakarta) marks a parameter/field for **cascade** and is the standard MVC trigger. In method validation you often use both: `@Validated` on the class, `@Valid` on a nested parameter. **When to use:** enforcing preconditions on service/component method arguments centrally, guarding public APIs of a module, validating configuration objects. Prefer it over scattering manual `if (x == null) throw`.
- Why doesn't validation fire when one method of the same bean calls another annotated method?It's proxy-based AOP. Self-invocation (this.other(...)) goes straight to the target instance, bypassing the proxy and its MethodValidationInterceptor. Only calls arriving through the injected proxy reference are intercepted.
- What exception is thrown, and how does it differ from MVC body validation?Method validation throws jakarta.validation.ConstraintViolationException. MVC @Valid request-body binding throws MethodArgumentNotValidException (a BindException subtype). Different mechanisms, different exceptions, different exception handlers.
saying these in an interview costs you the question
- Putting @Validated only on the method expecting it to trigger validation
- Expecting self-invoked methods to be validated
- Confusing ConstraintViolationException with MethodArgumentNotValidException
- Thinking @Valid on a class enables method validation (it's @Validated on the type)