← Back to the section

Data coming from a request can be anything. Bean Validation is a Jakarta EE standard that lets you describe the rules directly on the DTO class instead of cluttering the business logic with checks like if (name == null || name.isBlank()).

Why you need it

Without validation, every controller or service has to check the input itself. The result is repetitive, brittle code. Bean Validation solves the problem differently: the rules are declared once — on the data transfer object (DTO) class — and Spring applies them automatically before control reaches the controller method.

The dependency is already included in spring-boot-starter-web, so there is nothing extra to add.

Annotations: what to put on DTO fields

All annotations come from the jakarta.validation.constraints.* package:

public record CreateUserRequest(
    @NotBlank String username,
    @Email @NotBlank String email,
    @Size(min = 8, max = 72) String password,
    @Min(0) @Max(150) int age,
    @Pattern(regexp = "\\+\\d{7,15}") String phone
) {}

The short cheat sheet: @NotNull — the value is not null; @NotBlank — the string is not null and not empty (whitespace included); @Size — the length of a string or collection; @Min / @Max — a numeric range; @Email — email format; @Pattern — an arbitrary regular expression.

Each annotation accepts a message attribute to override the error text — read more about custom messages in the article on custom annotations and messages.

@Valid in the controller triggers the check

A single @Valid annotation in front of the parameter, and Spring validates the whole DTO before calling the method:

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    public ResponseEntity<Void> create(@Valid @RequestBody CreateUserRequest request) {
        // execution reaches here only if every check passed
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }
}

Without @Valid, the annotations on the DTO fields have no effect — Spring simply never runs the check.

Nested objects and cascading

If a DTO contains another object, the annotations on its fields will not fire automatically. You need to put @Valid on the field itself — then Spring validates the nested object cascadingly:

public record CreateOrderRequest(
    @NotNull @Valid AddressRequest address,
    @Size(min = 1) List<@Valid OrderItemRequest> items
) {}
public record AddressRequest(
    @NotBlank String city,
    @NotBlank String street
) {}

Without @Valid on the address field, validation stops at the CreateOrderRequest level and never descends into AddressRequest.

Validation groups

Sometimes one field needs to be validated differently depending on the operation. For this, Bean Validation supports groups:

public interface OnCreate {}
public interface OnUpdate {}

public record UserRequest(
    @NotNull(groups = OnCreate.class) String username,
    @NotBlank(groups = {OnCreate.class, OnUpdate.class}) String email
) {}

In the controller, instead of @Valid you use @Validated with the group specified:

@PostMapping
public ResponseEntity<Void> create(@Validated(OnCreate.class) @RequestBody UserRequest request) { ... }

@PutMapping("/{id}")
public ResponseEntity<Void> update(@PathVariable Long id,
                                   @Validated(OnUpdate.class) @RequestBody UserRequest request) { ... }

In practice, groups are rarely needed — more often separate DTOs for create and update are enough.

What happens on failure

If at least one constraint is violated, Spring throws MethodArgumentNotValidException. By default this is a 400 Bad Request with a bulky response body that is not very convenient for the client.

To return a clear error in a consistent format, you need a global handler — a @RestControllerAdvice with a method for MethodArgumentNotValidException. How to write it is described in the article REST API error handling in Java.

Deeper rules for structuring validation (which layer is responsible for what, when @Validated belongs on a service and when it does not) are in the validation style guide.

In short

  • @NotBlank, @Size, @Email, @Min/@Max, @Pattern — annotations from jakarta.validation.constraints.* describe the rules directly on the DTO.
  • @Valid in front of a method parameter triggers the check; without it the annotations do nothing.
  • For nested objects you need @Valid on the field itself — otherwise validation won't go any deeper.
  • On a violation Spring throws MethodArgumentNotValidException — handled in a @RestControllerAdvice.
  • Validation groups (@Validated(Group.class)) are rarely needed; in most cases separate DTOs are the better choice.
  • Where to validate in a Spring application — which layer is responsible for what.
  • Custom annotations and error messages — how to go beyond the standard constraints.
  • REST API error handling in Java — how to return a clean 400 with violation details.