skip to content

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