skip to content

Bean Validation (@Valid / BindingResult)

@Valid on a request body or model attribute triggers Jakarta validation, producing either a BindingResult you inspect or a MethodArgumentNotValidException you translate. Turning constraint violations into a useful 400 response is a very common practical task.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What does @Valid on a Spring MVC controller parameter do, and what happens when validation fails?

level: juniorimportance: must knowfreq 80%

answer

  1. @Valid triggers Hibernate Validator
  2. no BindingResult -> exception -> 400
  3. BindingResult must be right after the arg
  4. RequestBody -> MethodArgumentNotValidException
  5. ModelAttribute -> BindException

basics

~20 s

@Valid tells Spring to run Jakarta Bean Validation constraints (like @NotNull, @Size) on the argument. If a constraint fails and there is no BindingResult next to it, Spring throws an exception that becomes an HTTP 400 Bad Request.

solid answer

~40 s

@Valid (jakarta.validation.Valid) marks a controller argument for validation. When Spring binds a @RequestBody or @ModelAttribute, it invokes the JSR-380 Bean Validation provider (Hibernate Validator by default) to check the field constraints (@NotNull, @Size, @Email, @Min, etc.) on that object. If any constraint is violated and there is no BindingResult/Errors parameter immediately after the validated argument, Spring raises an exception — MethodArgumentNotValidException for @RequestBody, or BindException for @ModelAttribute form binding — which the default handler turns into a 400 Bad Request. If you add a BindingResult right after the @Valid argument, Spring instead injects the errors and lets your method decide what to do, so no exception is thrown.

code

java · 17 lines
java
public record CreateUser(
    @NotBlank String name,
    @Email String email,
    @Min(18) int age) {}

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

    // No BindingResult -> invalid body throws
    // MethodArgumentNotValidException -> 400 by default.
    @PostMapping
    ResponseEntity<Void> create(@Valid @RequestBody CreateUser body) {
        // reached only if body is valid
        return ResponseEntity.status(201).build();
    }
}

go deeper

for a junior

Know that @Valid runs the constraint annotations and a failure yields a 400.

for a middle

Explain the exception types (MethodArgumentNotValidException vs BindException) and the BindingResult opt-out.

for a senior

Discuss handling the exception centrally in @RestControllerAdvice vs in-method BindingResult, and the ordering gotcha.

for a principal

Reason about API error-contract design: consistent error envelopes, when to surface field errors vs generic messages, and provider configuration.

## What @Valid is `@Valid` comes from **Jakarta Bean Validation** (the spec formerly JSR-303/JSR-380), package `jakarta.validation.Valid` (older apps: `javax.validation.Valid`). Bean Validation is a standard for declaring **constraints** on object fields with annotations such as `@NotNull`, `@NotBlank`, `@Size`, `@Min`, `@Max`, `@Email`, `@Pattern`. A **validation provider** actually enforces them; the default in Spring Boot is **Hibernate Validator** (pulled in by `spring-boot-starter-validation`). ## What Spring MVC does with it When a controller method parameter is annotated with `@Valid` (or Spring's `@Validated`), Spring MVC runs the validator **after data binding** and **before your method body executes**: - For a **`@RequestBody`** parameter, the JSON is deserialized by Jackson into the object, then validated. - For a **`@ModelAttribute`** parameter (HTML form / query params), request parameters are bound onto the object, then validated. ## What happens on failure The outcome depends on whether you declare a `BindingResult`/`Errors` parameter **immediately after** the validated argument: 1. **No `BindingResult` present** → Spring throws: - `MethodArgumentNotValidException` for a `@RequestBody` argument, or - `BindException` for a `@ModelAttribute` argument. Both are handled by Spring's default exception resolver and produce **HTTP 400 Bad Request**. 2. **`BindingResult` present** → Spring does **not** throw. It populates the `BindingResult` with the errors and calls your method, so you can inspect `bindingResult.hasErrors()` and respond however you like (e.g., re-render a form). ## Key gotcha: order matters The `BindingResult` must come **directly after** the object it applies to. This is wrong: ```java public String submit(@Valid Order order, Model model, BindingResult result) // BROKEN ``` Here Spring can't associate `result` with `order`, so it throws instead of populating errors, and you may get a confusing exception. ## @Valid vs @Validated `@Valid` is the standard annotation. `@Validated` (`org.springframework.validation.annotation.Validated`) is Spring's variant that additionally supports **validation groups**. Both trigger validation on method arguments; use `@Validated` when you need groups. ## When to use which style - **REST APIs**: prefer letting `MethodArgumentNotValidException` propagate and handle it in a `@RestControllerAdvice` `@ExceptionHandler` to produce a consistent error body. Don't add `BindingResult` unless you truly need custom in-method logic. - **Server-rendered forms**: add `BindingResult` so you can re-display the form with field errors instead of a 400 page.

  • Why does adding a BindingResult parameter change the behavior?
    Its presence tells Spring you intend to handle errors yourself, so instead of throwing MethodArgumentNotValidException/BindException, Spring populates the BindingResult and calls your method. You then check hasErrors().
  • Which dependency provides the validator in Spring Boot?
    spring-boot-starter-validation, which brings in Hibernate Validator (the reference implementation of Jakarta Bean Validation). Without it, constraint annotations are ignored.

saying these in an interview costs you the question

  • Thinking @Valid returns a boolean or that you must call it manually
  • Believing validation still works after removing spring-boot-starter-validation
  • Putting BindingResult anywhere except immediately after the validated argument
  • Assuming @Valid validates path variables or simple @RequestParam values (that needs @Validated on the class + method-level validation)

context

open as a page

How do you produce a clean JSON error response from MethodArgumentNotValidException for a REST API?

level: seniorimportance: must knowfreq 70%

basics

~10 s

Add a @RestControllerAdvice with an @ExceptionHandler(MethodArgumentNotValidException.class). Read ex.getBindingResult().getFieldErrors(), map each to field + message, and return a 400 with that structured body.

open as a page

How does validation differ between @RequestBody and @ModelAttribute, and which exceptions result?

level: middleimportance: should knowfreq 60%

basics

~10 s

@RequestBody validates a JSON body and throws MethodArgumentNotValidException on failure; @ModelAttribute validates form/query binding and throws BindException. Both become HTTP 400. @ModelAttribute can also carry type-conversion (binding) errors, not just constraint violations.

open as a page

What are validation groups, and how do you apply different constraints for create vs update using @Validated?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Validation groups let one class hold constraints that apply only in certain scenarios. You tag a constraint with groups = Create.class, then trigger it with Spring's @Validated(Create.class) on the parameter. Plain @Valid can't select groups.

open as a page

How do you validate a cross-field rule and when should validation live in the service layer instead of the controller?

level: principalimportance: should knowfreq 35%

basics

~20 s

Cross-field rules use a class-level constraint (a custom annotation + ConstraintValidator on the whole object), surfacing as a global error. Input-shape validation belongs at the controller edge; business rules needing DB/state belong in the service.

open as a page