How do you validate incoming request data in Spring Boot (Bean Validation, @Valid)?
Quick Answer
Spring Boot integrates the Bean Validation standard (Jakarta Validation, implemented by Hibernate Validator) via annotations like @NotNull, @NotBlank, @Size, and @Email placed directly on a request DTO's fields. Adding @Valid (or @Validated) before a @RequestBody parameter in a controller method tells Spring MVC to run those validation rules automatically before the handler executes; if validation fails, Spring throws a MethodArgumentNotValidException, which you typically handle globally via @ExceptionHandler/@ControllerAdvice to return a structured 400 Bad Request response.
Detailed Answer
Spring Boot integrates the Bean Validation standard (Jakarta Validation API, typically implemented by Hibernate Validator, pulled in automatically by spring-boot-starter-validation) for declarative, annotation-based input validation:
1. Annotate the request DTO's fields:
record CreateOrderRequest(
@NotBlank String customerId,
@Min(1) int quantity,
@Email String contactEmail
) { }
2. Add @Valid in the controller to trigger validation on that parameter:
@RestController
class OrderController {
@PostMapping("/orders")
Order createOrder(@Valid @RequestBody CreateOrderRequest request) {
// if we reach this line, request has already passed all validation rules
}
}
If validation fails, Spring MVC throws MethodArgumentNotValidException before the handler method body ever executes — the controller code never has to manually check if (quantity < 1) ....
3. Handle validation failures globally, converting them into a consistent, structured error response instead of a generic 500:
@RestControllerAdvice
class ValidationExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
ResponseEntity<Map<String, String>> handleValidation(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors()
.forEach(err -> errors.put(err.getField(), err.getDefaultMessage()));
return ResponseEntity.badRequest().body(errors); // 400, with field-level error details
}
}
Common annotations: @NotNull/@NotBlank/@NotEmpty (subtly different: @NotNull allows empty strings, @NotBlank doesn't allow blank/whitespace-only strings, @NotEmpty disallows empty-but-not-null collections/strings), @Size(min=, max=), @Min/@Max, @Email, @Pattern(regexp=).
Nested object validation: if a DTO contains another validated object as a field, add @Valid on that nested field too, or the nested object's own constraints won't be checked — @Valid on the controller parameter alone only validates that top-level object's direct fields plus explicitly @Valid-annotated nested fields.
@Validated vs @Valid: @Valid is the standard JSR-380 annotation; @Validated is a Spring-specific variant that additionally supports validation groups and enables method-level validation (validating parameters of a plain @Service method, not just @RequestBody controller arguments).