Skip to main content
Spring Framework · Topic 11 of 25

Bean Validation

Definition​

Bean Validation lets you put rules on fields with annotations such as @NotBlank and @Size. Adding @Valid to a controller parameter checks them and gives you clear field errors.

Why it exists​

Bad input that reaches business code fails in confusing places or corrupts data. Declaring rules next to the fields keeps them visible and gives callers precise errors.

How it works​

Groups​

Groups apply different rules for create versus update:

interface OnCreate {}
interface OnUpdate {}

record UserDto(
@Null(groups = OnCreate.class) @NotNull(groups = OnUpdate.class) Long id,
@NotBlank String name) {
}

Use @Validated(OnCreate.class) on the parameter. Often separate request DTOs for create and update are simpler than groups.

Example code​

Validating a request body​

public record CreateOrderRequest(
@NotNull Long customerId,
@NotEmpty @Size(max = 50) List<@Valid OrderLine> lines,
@Email String notificationEmail) {

public record OrderLine(
@NotBlank String sku,
@Positive int quantity) {
}
}
@RestController
@RequestMapping("/orders")
class OrderController {

@PostMapping
ResponseEntity<Void> create(@Valid @RequestBody CreateOrderRequest request) {
// request is valid here
return ResponseEntity.accepted().build();
}
}

@Valid on the parameter triggers validation; @Valid on OrderLine inside the list cascades into each element. Without the cascade, nested fields are silently unchecked, which is a classic bug.

Validating path variables and request parameters​

Put @Validated on the controller class, then use constraints directly on parameters:

@Validated
@RestController
class ProductController {

@GetMapping("/products/{id}")
Product get(@PathVariable @Min(1) long id,
@RequestParam @Size(min = 2, max = 20) String locale) {
// ...
return null;
}
}

Failures here throw ConstraintViolationException (or HandlerMethodValidationException in Spring 6.1+ when using method validation built into MVC). Body failures throw MethodArgumentNotValidException.

Returning field errors​

@RestControllerAdvice
class ValidationHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
ProblemDetail onInvalid(MethodArgumentNotValidException ex) {
ProblemDetail pd = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Validation failed");
Map<String, String> errors = new LinkedHashMap<>();
ex.getBindingResult().getFieldErrors()
.forEach(e -> errors.putIfAbsent(e.getField(), e.getDefaultMessage()));
pd.setProperty("errors", errors);
return pd;
}
}

If your global advice already extends ResponseEntityExceptionHandler (as in exception handling), do not add a second advice for the same exception. Override handleMethodArgumentNotValid in that class instead, so only one handler owns the response.

Custom constraint​

@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = SkuValidator.class)
public @interface ValidSku {
String message() default "SKU must look like ABC-12345";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class SkuValidator implements ConstraintValidator<ValidSku, String> {

private static final Pattern PATTERN = Pattern.compile("[A-Z]{3}-\\d{5}");

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value == null || PATTERN.matcher(value).matches(); // null handled by @NotNull
}
}

Convention: constraint validators treat null as valid and let @NotNull handle presence. Validators can inject Spring beans (for example to check uniqueness), but keep them cheap; uniqueness must still be enforced by a database constraint because of race conditions.

System design considerations​

  • Back important rules with database constraints too, since the database is the last line of defence
  • Return a stable error format (ProblemDetail) that clients can parse
  • Validate messages from queues as strictly as HTTP input

When to use it, and when not​

Use it when

  • You accept external input (REST bodies, parameters, messages)
  • You return consistent field-level errors
  • You enforce a rule that several endpoints share (custom constraint)

Think twice when

  • The rule needs database state or heavy logic, which belongs in the service layer
  • You would rely on validation annotations as your only integrity guarantee
  • You expose entities directly instead of request DTOs

Common mistakes​

  • Missing @Valid on nested objects or collection elements.
  • Using @NotNull on primitives (they can never be null).
  • Mutating entities directly from request DTOs and validating the entity afterwards.
  • Relying on application-level uniqueness checks without a database unique index.

Interview questions​

1. @Valid vs @Validated?

@Valid is the standard Jakarta annotation: triggers validation and cascades into nested objects. @Validated is Spring's variant: supports validation groups and, on a class, enables method-level validation of parameters like @PathVariable.

2. @NotNull vs @NotEmpty vs @NotBlank?

@NotNull: not null. @NotEmpty: not null and size greater than 0 (strings, collections, maps, arrays). @NotBlank: strings only, not null and has at least one non-whitespace character.

3. Is validation in the controller enough?

No. Validate at the edge for fast feedback, but enforce invariants in the domain and database too (unique indexes, not-null columns, foreign keys), because other entry points such as messages and batch jobs bypass the controller.

4. How do you validate a List<String> of emails?

Container element constraints: List<@Email String>.

5. What exception does a failed @RequestBody validation throw, and how do you handle it?

MethodArgumentNotValidException. Handle it in a @RestControllerAdvice and return 400 with the field errors.

Check yourself​

Quick quiz3 questions

1What makes a @RequestBody validated in a controller?

2Which exception does Spring MVC raise when body validation fails?

3How do you validate @PathVariable and @RequestParam?

Key takeaways​

  • Annotate DTOs with constraints, trigger with @Valid, cascade explicitly.
  • Add @Validated on classes for parameter-level constraints.
  • Return structured field errors with a single advice class.
  • Validation at the edge complements, never replaces, database constraints.

Next: Testing basics.

Next topicREST exception handling

Finished this page? Track your progress (saved only in your browser).