How does validation differ between @RequestBody and @ModelAttribute, and which exceptions result?
answer
- RequestBody -> Jackson -> MethodArgumentNotValidException
- ModelAttribute -> WebDataBinder -> BindException
- malformed JSON -> HttpMessageNotReadableException (pre-validation)
- form typeMismatch = binding error in BindingResult
- Spring 6: MANVE extends BindException
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.
solid answer
~40 sBoth use the same Bean Validation constraints, but the binding path and failure type differ. With `@RequestBody`, Jackson deserializes JSON into the object; malformed JSON fails before validation (HttpMessageNotReadableException), and constraint failures raise `MethodArgumentNotValidException`. With `@ModelAttribute`, Spring binds request parameters/form fields via a `WebDataBinder`, so you can get **two kinds** of errors in the same `BindingResult`: type-conversion/binding errors (e.g., "abc" into an int → a field error) and Bean Validation constraint errors; without a `BindingResult` these surface as `BindException`. Both exceptions extend from Spring's error infrastructure and map to HTTP 400 by default. In Spring 6, `MethodArgumentNotValidException` actually extends `BindException`, so a single handler for `BindException` catches both. For forms you typically add a `BindingResult` to re-render; for REST you let the exception hit a `@RestControllerAdvice`.
code
java · 16 lines// REST: exception-driven
@PostMapping("/api/orders")
Order createApi(@Valid @RequestBody OrderDto dto) { ... }
// invalid -> MethodArgumentNotValidException -> 400
// Form: BindingResult-driven
@PostMapping("/orders")
String createForm(@Valid @ModelAttribute OrderForm form,
BindingResult result,
Model model) {
if (result.hasErrors()) {
return "order-form"; // re-render with field errors
}
// ... save
return "redirect:/orders";
}go deeper
Know both paths end in a 400 and use the same constraint annotations.
Name the two distinct exceptions and that malformed JSON is a separate error.
Explain binding-vs-constraint errors in BindingResult and the Spring 6 exception hierarchy.
Design a unified error contract covering parse errors, binding errors, and constraint errors across both intake styles.
## Two binding mechanisms, one validation engine Jakarta Bean Validation constraints (`@NotBlank`, `@Size`, `@Email`, ...) are identical regardless of source. What differs is **how the object gets populated** and **what exception fires**. ### @RequestBody (JSON / typically REST) 1. `HttpMessageConverter` (Jackson `MappingJackson2HttpMessageConverter`) deserializes the request body into the target type. 2. If the JSON is syntactically broken or a type can't be parsed (e.g., a string where a number is required), you get **`HttpMessageNotReadableException`** — this happens **before** Bean Validation, so your `@NotNull` etc. never run for that field. 3. If deserialization succeeds, `@Valid`/`@Validated` triggers the validator. Constraint violations → **`MethodArgumentNotValidException`**, which exposes a `BindingResult` via `getBindingResult()`. ### @ModelAttribute (form fields / query params) 1. A `WebDataBinder` maps individual request parameters onto the object's properties using `PropertyEditor`/`Converter` conversion. 2. **Binding errors** (type mismatch, e.g., `age=abc` into an `int`) are recorded as field errors in the `BindingResult` — these are `FieldError`s with codes like `typeMismatch`, and validation still runs on the fields that did bind. 3. `@Valid`/`@Validated` then adds **constraint violations** to the same `BindingResult`. 4. With no `BindingResult` parameter → **`BindException`**. ## Spring 6 hierarchy detail In Spring Framework 6 / Boot 3, `MethodArgumentNotValidException` **extends `BindException`** (both under `org.springframework.validation` / `web.bind`). Practically, an `@ExceptionHandler(BindException.class)` will catch both, and both give you `getBindingResult()`, `getFieldErrors()`, `getGlobalErrors()`. ## Reading the errors From the `BindingResult` you can extract: - `getFieldErrors()` → `FieldError` (field name, rejected value, default message, error codes). - `getGlobalErrors()` → `ObjectError` (class-level constraints, e.g., a custom cross-field `@PasswordsMatch`). ## Gotchas - **Malformed JSON is not a validation error.** Don't expect `@NotNull` messages for a body that failed to parse — handle `HttpMessageNotReadableException` separately. - **Type mismatch on JSON vs form differ.** `age: "abc"` in JSON → `HttpMessageNotReadableException`; `age=abc` as a form param → a binding `FieldError` inside the `BindingResult`. - **Nested validation** needs `@Valid` on the nested field for cascading; it is not automatic. - **@ModelAttribute is the default** for non-simple, non-body types, so a plain object parameter without `@RequestBody` is treated as a model attribute. ## When to use which handling style - **REST**: no `BindingResult`; centralize with `@RestControllerAdvice` handling `MethodArgumentNotValidException` (and optionally `HttpMessageNotReadableException`). - **Server-side forms**: include `BindingResult` and re-render the view with errors, since users need inline field feedback.
- If someone sends `{"age": "twenty"}` to a @RequestBody endpoint expecting an int age, do your @Min constraints fire?No. Jackson fails to deserialize, throwing HttpMessageNotReadableException before Bean Validation runs. You must handle that exception separately; the @Min message never appears.
- How can a single @ExceptionHandler cover both form and body validation failures?Handle BindException — in Spring 6 MethodArgumentNotValidException extends it, so one handler catches both and you read getBindingResult()/getFieldErrors().
saying these in an interview costs you the question
- Claiming malformed JSON produces MethodArgumentNotValidException
- Thinking @ModelAttribute failures throw MethodArgumentNotValidException
- Assuming type-mismatch and constraint errors are the same thing
- Believing nested objects are validated automatically without @Valid on the field