Search code examples
springspring-mvcdata-bindingannotations

Spring MVC: @RequestBody VS @ModelAttribute


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?


Solution

  • 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 ) {
         // ...   
    }