Validating Request Bodies with @Valid
Validating Request Bodies with @Valid
You have learned how to annotate a model class with Bean Validation constraints. The next critical step is triggering that validation inside a Spring MVC controller. Without an explicit trigger, the constraints sit inert — Spring deserialises the JSON payload and hands you the object regardless of whether its fields are valid. This lesson covers how @Valid and @Validated tell Spring to run the validator before the method body executes, what happens when validation fails, and the patterns you should follow to keep your controllers clean.
Ensure the Starter Is Present
Before anything else: Spring Boot 3 does not include validation support in spring-boot-starter-web. You must add the dedicated starter explicitly, or @Valid is silently ignored and constraints never run.
This pulls in Hibernate Validator, the reference implementation of Jakarta Validation 3.0. Once it is on the classpath, Spring auto-configures a LocalValidatorFactoryBean and wires it into the MVC layer automatically.
The Anatomy of a Validated Controller Method
Consider a user-registration endpoint. The request body is a RegisterRequest DTO decorated with Bean Validation constraints. Placing @Valid immediately before the @RequestBody parameter instructs Spring's argument resolver to pass the deserialised object through the validator before the method receives it.
The DTO itself carries the constraint annotations:
@Valid comes from the Jakarta Validation spec (jakarta.validation.Valid) and triggers validation on the annotated object, including cascading into any nested objects also marked @Valid. @Validated is a Spring-specific annotation that adds support for validation groups (covered in Lesson 9). For plain request-body validation, @Valid is the standard, spec-compliant choice.
What Happens When Validation Fails
When at least one constraint is violated, Spring throws MethodArgumentNotValidException. By default this produces a 400 Bad Request response. The exception carries a BindingResult holding every individual field error. Your method body is never called — the exception is raised by the framework before control reaches your code.
Capturing BindingResult Manually
There is a second, less common pattern: declare a BindingResult parameter immediately after the @RequestBody parameter. When you do this, Spring suppresses the automatic exception and lets you inspect the errors yourself inside the method body.
@ControllerAdvice (Lesson 7) that catches MethodArgumentNotValidException globally. Only use BindingResult when you genuinely need per-endpoint logic that a global handler cannot express.
Validating Nested Objects
If your DTO contains a nested object, apply @Valid to the nested field. Without it, the constraints on the nested class are silently ignored.
The @Valid annotation on a collection type parameter (List<@Valid LineItem>) uses container element validation introduced in Bean Validation 2.0 and fully supported in Spring Boot 3. Hibernate Validator validates every element in the list and collects all violations before throwing.
Validating Path Variables and Query Parameters
Bean Validation is not limited to request bodies. You can apply constraint annotations directly to method parameters for path variables and request parameters. For this to work, add @Validated at the class level — this tells Spring to create a proxy around the controller that intercepts method calls and checks parameter-level constraints.
When a path variable or query parameter constraint is violated, Spring throws a ConstraintViolationException rather than MethodArgumentNotValidException. This distinction matters when writing a global exception handler: you need to handle both types to give clients consistent error responses across all input vectors.
@RequestBody type. This tightly couples your API contract to your database schema. Define dedicated request DTOs — Java records work cleanly in Java 16+ — and map them to entities in the service layer. You retain full control over what fields are exposed, what constraints apply at the API boundary, and how errors are reported to clients.
Putting It Together: Request Flow
When a POST request arrives at /api/users the following sequence occurs:
- Deserialization: Jackson reads the JSON body and creates a
RegisterRequestinstance. - Validation: Because
@Validis present, Spring invokes Hibernate Validator on that instance. - Violation found? Spring throws
MethodArgumentNotValidException; the method body is skipped. - No violations: The validated object is passed to
register(), which delegates to the service layer.
This pipeline keeps validation concerns entirely outside your business logic. Your service methods can trust that every RegisterRequest they receive already satisfies the declared constraints — no defensive null-checks or manual field inspection needed.
Summary
Triggering validation in a Spring controller requires three things: spring-boot-starter-validation on the classpath, constraint annotations on your DTO, and @Valid placed before the @RequestBody parameter. When a constraint fails, Spring raises MethodArgumentNotValidException before your method body runs. Nested objects require their own @Valid to cascade validation. For path variables and query parameters, use class-level @Validated alongside per-parameter constraints, and be aware that violations produce ConstraintViolationException instead. The next lessons focus on catching these exceptions and shaping the error responses your API clients actually receive.