4
votes

Did I understood it correct that in order to catch/data-bind the body of a HTTP Request in a Spring MVC application somone can use...

@RequestBody

for requests encoded as application/json?

@PostMapping(consumes = "application/json")
public String handleUpload( @RequestBody UploadCommand command ) {
     // ...   
}

@ModelAttribute

for requests encoded as x-www-form-urlencoded or multipart/form-data?

@PostMapping(consumes = "multipart/form-data")
public String handleUpload( @ModelAttribute UploadCommand command ) {
     // ...   
}

Questions:

Why is it necessary for Spring to have those two different annotations?

Are there any other use cases for those annotations?

NOTE: After digging around: This stackoverflow answer elaborates on @ModelAttribute in depth: @ModelAttribute annotation, when to use it?

1
Because both ar quite different beast. creating an object from JSON is something completely different then binding from request parameters. Hence different annotations. - M. Deinum
@M. Deinum Can the query part of an url (?..) be mapped as well with ModelAttribute (into a single object)? - Dachstein
That is the whole point of @ModelAttribute to do that. Can be either query or form parameters . - M. Deinum
@ModelAttribute dose nothing with data binding. You can remove this annotation, but data will be bind anyway. @ModelAttribute just check if marked object is already in model and create new one if it is not. - Ken Bekov
The big difference is @RequestBody uses a messageConverter and @ModelAttribute don't. - akuma8

1 Answers

2
votes

Why is it necessary for Spring to have those two different annotations?

Two annotations are created for different application types.
- @RequestBody for restfull applications
- @ModelAttribute for web mvc application

What are their differences?

Suppose you have a java class UserData:

public class UserData {

    private String firstName;
    private String lastName;

    //...getters and setters
} 

You want to consume requests with this user data and map to your object fields.

@RequestBody is used for consuming request body and to deserialize into an Object through an HttpMessageConverter. You can provide data types that @PostMapping can accept by specifing "consumes" in this annotation.

Ref: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-requestbody

Example of the POST request with user data json body:

POST /api/v1/auth HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 40
Accept: application/json, text/plain, */*
Content-Type: application/json

{"firstName":"Tyrion","lastName":"Lannister"}

You can simply annotate your method argument with annotation @RequestBody and all data will be converted in your model

@PostMapping("/user")
public void getUserData( @RequestBody UserData userData) {
     // ...   
}

Otherwise, you have to consume your request as string and then manually do deserialization by yourself:

ObjectMapper objectMapper = new ObjectMapper();
UserData userData = objectMapper.readValue(postBody, UserData.class)

@ModelAttribute is an enhancement for ServletRequest that saves you from having to deal with parsing and converting individual query parameters and form fields. You simply annotate your request body with this annnotation and don't need any more to do this:

String firstName= req.getParameter("firstName"); // req is HttpServletRequest
String lastName= req.getParameter("lastName"); // req is HttpServletRequest

All data will be converted by spring automatically.

Ref: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-modelattrib-method-args

Example of the form for this request is following:

<form action="yourEndpoint" method="POST">
    <input name="firstName" id="firstName" value="Tyrion">
    <input name="lastName" id="lastName" value="Lannister">
    <button>Submit</button>
</form>

This form will be converted by web browser to the following request that will be consumbed by spring:

POST / HTTP/2.0
Host: foo.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

firstName=Tyrion&lastName=Lannister

Example of spring mvc controller:

@PostMapping("/user")
public void getUserData( @ModelAttribute UserData userData ) {
     // ...   
}