What is the difference between @RequestBody and @ModelAttribute in a Spring MVC controller?
answer
- @RequestBody = body + HttpMessageConverter (Jackson)
- @ModelAttribute = params + WebDataBinder + setters
- InitBinder/allowedFields only touch ModelAttribute path
- Content-Type picks the converter
- JSON→RequestBody, HTML form→ModelAttribute
basics
~10 s@RequestBody reads the raw request body (usually JSON) and deserializes it into an object using a message converter. @ModelAttribute binds request parameters (query string or HTML form fields) onto an object via its setters.
solid answer
~30 s@RequestBody takes the whole HTTP request body and converts it into an object using an HttpMessageConverter chosen by the Content-Type header — typically MappingJackson2HttpMessageConverter for application/json. It ignores individual request parameters. @ModelAttribute works differently: it creates/obtains a target object and uses a WebDataBinder to bind name-matched request parameters (query params and application/x-www-form-urlencoded or multipart form fields) onto its properties through setters, applying type conversion. So @RequestBody is for JSON/XML API payloads and does NOT use WebDataBinder; @ModelAttribute is for classic HTML form submissions and DOES use WebDataBinder, which is what @InitBinder, allowed-field lists, PropertyEditors and the ConversionService customize.
code
java · 16 lines@RestController
public class UserController {
// JSON body -> Jackson deserialization, no WebDataBinder
@PostMapping(value = "/api/users", consumes = "application/json")
public User createJson(@RequestBody UserDto dto) {
return service.create(dto);
}
// HTML form fields / query params -> WebDataBinder, setters
@PostMapping(value = "/users", consumes = "application/x-www-form-urlencoded")
public String createForm(@ModelAttribute UserForm form) {
service.create(form);
return "redirect:/users";
}
}go deeper
Know the one-line distinction: body/JSON vs form/params, and that Content-Type matters.
Explain that they use different resolvers (HttpMessageConverter vs WebDataBinder) and that customization hooks differ.
Discuss error semantics (HttpMessageNotReadableException vs BindingResult) and why InitBinder/editors don't touch RequestBody.
Weigh DTO design, over-posting risk on each path, and when to standardize on JSON vs form binding across an API surface.
## The two binding paths Spring MVC has **two completely separate mechanisms** for turning an HTTP request into a Java object, and knowing which one runs decides which customization hooks apply. ### @RequestBody — message conversion `@RequestBody User u` tells Spring to read the **entire raw HTTP request body** (the bytes after the headers) and deserialize it. Spring picks an `HttpMessageConverter` by matching the request's `Content-Type` header. For `application/json` that is normally `MappingJackson2HttpMessageConverter`, which delegates to a Jackson `ObjectMapper`. For XML it might be a Jackson XML or JAXB converter. Key points: - It reads the **body**, not query parameters or form fields. `?name=Bob` is invisible to `@RequestBody`. - The parsing rules come from **Jackson** (e.g. `@JsonProperty`, `@JsonIgnore`, `FAIL_ON_UNKNOWN_PROPERTIES`), **not** from `WebDataBinder`. - `@InitBinder`, `setAllowedFields`, `PropertyEditor`s do **NOT** affect `@RequestBody`. Those only touch the `WebDataBinder` path. - The body can be read only once; you generally have exactly one `@RequestBody` parameter per handler. ### @ModelAttribute — data binding via WebDataBinder `@ModelAttribute User u` (the annotation is optional for non-simple types — a bare `User u` parameter is treated as a model attribute) does this: 1. Obtains a target instance (new instance, or from the model / `@SessionAttributes`). 2. Creates a `WebDataBinder` around it. 3. Binds **request parameters** — the query string plus `application/x-www-form-urlencoded` or `multipart/form-data` form fields — onto the object by **matching parameter names to property names** and calling **setters**. 4. Converts each string value to the target property type using `PropertyEditor`s and the `ConversionService`. This is the mechanism customized by `@InitBinder`, `binder.setAllowedFields(...)`, `binder.registerCustomEditor(...)`, and registered `Converter`/`Formatter` beans. ### Content-Type decides which one you should use - Browser HTML form POST (`application/x-www-form-urlencoded`) or file upload (`multipart/form-data`) → `@ModelAttribute`. - JSON/XML REST payload (`application/json`) → `@RequestBody`. Sending JSON to a handler that expects `@ModelAttribute` silently binds nothing (no matching params); sending form fields to a `@RequestBody` handler yields a 415 or empty object. ### Gotcha: validation is separate Both support `@Valid`/`@Validated`, but *how* errors surface differs: a bind/conversion failure on `@ModelAttribute` populates `BindingResult` (recoverable if you add a `BindingResult` parameter); a malformed `@RequestBody` throws `HttpMessageNotReadableException` before any binding. (Validation triggering itself is covered by the Validation leaf.)
- Does @InitBinder affect a @RequestBody parameter?No. @InitBinder configures the WebDataBinder, which is only used for @ModelAttribute / @RequestParam / @PathVariable binding. @RequestBody is handled by HttpMessageConverters (Jackson), so InitBinder, custom editors and allowed-field lists have no effect on it.
- If a request sends both a JSON body and query parameters, can one handler read both?Yes: a handler can have a @RequestBody parameter (reads the body) and separate @RequestParam parameters (read the query string) at the same time — they use different resolvers. You just cannot bind the query params into the @RequestBody object.
saying these in an interview costs you the question
- Claiming @RequestBody uses WebDataBinder or PropertyEditors
- Thinking @ModelAttribute reads the JSON request body
- Believing @InitBinder customizes @RequestBody deserialization
- Saying both read from the same source (query params)