Bean Validation has been part of the Java ecosystem since JSR-303 shipped in 2009, matured into JSR-380 (Bean Validation 2.0), and now lives on as Jakarta Bean Validation 3.0, published in October 2020 as part of Jakarta EE 9. Spring Boot uses Hibernate Validator as the reference implementation, but since Spring Boot 2.3 the spring-boot-starter-validation dependency is no longer pulled in automatically by spring-boot-starter-web — teams upgrading from 2.2 have shipped controllers where every @Valid annotation silently does nothing because the validator provider was never on the classpath. That single dependency change has quietly reintroduced unvalidated input into production APIs more than once. The annotations themselves — @NotNull, @NotBlank, @Size, @Pattern, @Email — are declarative and easy to reason about individually, which is exactly why teams over-trust them: a form field can carry three constraint annotations and still let malformed or malicious data through because the containing object, the list it lives in, or the service method that receives it was never wired into the validation pipeline in the first place. This piece walks through how Bean Validation actually triggers in a Spring Boot request lifecycle, and the specific, currently-documented places where that trigger doesn't fire the way most developers assume.
What actually triggers Bean Validation in a Spring Boot controller?
Validation triggers only when @Valid (from jakarta.validation) or Spring's @Validated sits directly on the parameter Spring is binding — not implicitly because the target class happens to carry constraint annotations. On a @RestController method, annotating a @RequestBody parameter with @Valid causes Spring's RequestResponseBodyMethodProcessor to run the bound object through the configured Validator after deserialization, and a failure throws MethodArgumentNotValidException. That exception is not automatically turned into a clean 400 response with field-level detail — left unhandled, or handled naively, it can leak stack traces or internal class names to the client. Production APIs need an explicit @ExceptionHandler or a global @ControllerAdvice that catches MethodArgumentNotValidException (and ConstraintViolationException for method-level validation) and maps it to a sanitized error payload. Teams that add @Valid but never add the corresponding exception handler often ship inconsistent behavior: validation runs correctly, but the failure response format differs from every other error path in the API, which is itself an information-disclosure and consistency problem.
Why does validation silently skip nested objects and DTOs?
Hibernate Validator's own specification is explicit that cascaded validation only follows references marked @Valid — a field of a composite type is not validated by default just because its class carries constraints. If an OrderRequest DTO has a field private Address shippingAddress, and Address itself has @NotBlank on city and postalCode, none of those constraints run unless shippingAddress is itself annotated @Valid inside OrderRequest. Miss that one annotation on one nested field, three levels deep in a DTO graph, and the outer object reports "valid" while carrying a completely empty inner object. This is one of the most common real-world bypasses because it produces no error, no warning, and no test failure unless the test specifically constructs a malformed nested object and asserts a validation failure — most integration tests only exercise the happy path for nested fields, so the gap survives code review and QA alike.
Where does list and collection validation break down?
Validating List<T> elements is one of the more inconsistent corners of Spring's validation integration, and it is not just folklore — it's tracked in open Spring Framework issues. Spring Framework issues #32886 and #31870 document cases where validation groups and per-element constraints on List<SomeDto> request-body fields don't fire as expected depending on how the controller method and the DTO are structured, particularly when validation groups are involved. In practice this means a request body like {"items": [{"sku": ""}, {"sku": "valid"}]} can pass validation even though the first list element violates an @NotBlank constraint on sku, purely because of how the collection is bound versus how a single nested object is bound. Because this is a known gap rather than a simple misconfiguration, the safest mitigation is not to assume @Valid on a List<T> field behaves identically to @Valid on a single nested object — write an explicit test that submits an invalid element inside a valid collection and confirms the request is rejected before trusting that path in production.
Why does @Validated on a service method sometimes do nothing?
Method-level validation — annotating parameters of a @Service bean directly with constraints like @NotNull or @Min — depends entirely on Spring AOP, and AOP proxies only get created when the class itself carries the class-level @Validated annotation (Spring's own annotation, not jakarta.validation.Valid). Add @NotBlank to a method parameter on a service class and forget the class-level @Validated, and Spring never wraps the bean in the proxy that intercepts the call — the constraint annotation sits there as inert documentation, no ConstraintViolationException is ever thrown, and the method executes with whatever was passed in. This misconfiguration is especially dangerous because it typically passes code review: the annotations are present and look correct at the call site, and nothing fails until specific bad input reaches that exact method in production. It's also invisible to controller-level testing, since the controller's own @Valid on the request body may have already passed, masking the fact that the service-layer constraint never actually ran.
Can Bean Validation alone stop a business-logic bypass?
No — Bean Validation constrains shape and format, not business meaning, and conflating the two is a common source of false confidence. @Positive on an amount field and @Future on a deliveryDate field both pass Hibernate Validator's checks for a transfer request where the destination account equals the source account, or where a discount code is combined with a promotion it was never meant to stack with — those are semantic, cross-field, or stateful rules that the standard annotation set was never designed to express. Bean Validation does support custom constraints through ConstraintValidator implementations, and cross-field rules via class-level @Constraint annotations that receive the whole object rather than a single field, which is the correct mechanism for this class of rule. Teams that rely exclusively on the built-in annotation catalog and treat "passes @Valid" as equivalent to "is a legitimate request" are the ones most likely to discover a business-logic bypass in production rather than in review, because the gap between format-valid and semantically-valid input is exactly where that class of vulnerability lives.