8

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?

Community
  • 1
  • 1
Dachstein
  • 3,994
  • 6
  • 34
  • 61
  • 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 May 01 '17 at 09:14
  • @M. Deinum Can the query part of an url (?..) be mapped as well with ModelAttribute (into a single object)? – Dachstein May 01 '17 at 09:20
  • That is the whole point of `@ModelAttribute` to do that. Can be either query or form parameters . – M. Deinum May 01 '17 at 09:26
  • 1
    `@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 May 01 '17 at 10:44
  • 1
    The big difference is `@RequestBody` uses a `messageConverter` and `@ModelAttribute` don't. – akuma8 May 01 '17 at 11:10
  • @Ken what you mean by "already in model"? – Dachstein May 01 '17 at 11:39
  • @Dachstein object can be added by another method marked with `@ModelAttribute`. Or object can be in the session. That's all mean that object already in model and Spring will just pass this existing object to the method. – Ken Bekov May 01 '17 at 11:43
  • @Ken Got your point. I looked into this answer: http://stackoverflow.com/questions/8688135/modelattribute-annotation-when-to-use-it/26916920#26916920 – Dachstein May 01 '17 at 11:44

1 Answers1

8

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 ) {
     // ...   
}
Dmitrii Cheremisin
  • 1,498
  • 10
  • 11
  • nice explanation! thanks! and that after 3 years I asked the question and I still learned something. – Dachstein May 16 '20 at 14:33
  • @Dachstein, haha =) I answered it for history=) I see it has a lot of views, so a lot of people have the same problem as yours) you can mark it as answer if everything is correct ;-) – Dmitrii Cheremisin May 17 '20 at 11:19