1. Overview

In this tutorial, we'll investigate the RequestBodyAdvice interface in Spring MVC.

By using its implementations, we can customize how Spring MVC reads the request body.

2. Sample Application

Let's start with our sample application.

@RestController
public class QuestionController {

    @PostMapping(value = "/ask", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public Answer ask(@RequestBody Question question) {
        System.out.println("In controller method");
    
        Answer answer = new Answer();
        answer.setAnswerMessage("I don't know!");
        answer.setQuestion(question);
        return answer;
    }
}

In the QuestionController class, we have a single endpoint which reads a Question from the request body and returns an Answer in the response body. Note the usage of @RequestBody in the controller method. This annotation tells Spring MVC to read the request body, convert it to Question and assign the converted object to the question variable.

public class Question {

    private String questionMessage;
    private Date date;

    // Getters & setters
}

public class Answer {

    private String answerMessage;
    private Question question;

    // Getters & setters
}

Then we have the Question and Answer classes which hold the related fields.

3. Implementing RequestBodyAdvice

RequestBodyAdvice allows customization of the request body before it is converted into an Object. Additionally, we can modify the converted object before it is passed into our controller methods.

Let's provide a basic implementation to investigate the interface methods:

@ControllerAdvice
public class CustomRequestBodyAdvice implements RequestBodyAdvice {

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        System.out.println("In supports() method of " + getClass().getSimpleName());
        return methodParameter.getContainingClass() == QuestionController.class && targetType.getTypeName() == Question.class.getTypeName();
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                           Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        System.out.println("In beforeBodyRead() method of " + getClass().getSimpleName());
        return inputMessage;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                Class<? extends HttpMessageConverter<?>> converterType) {
        System.out.println("In afterBodyRead() method of " + getClass().getSimpleName());
        if (body instanceof Question) {
            Question question = (Question) body;
            question.setDate(new Date());
            return question;
        }

        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                  Class<? extends HttpMessageConverter<?>> converterType) {
        System.out.println("In handleEmptyBody() method of " + getClass().getSimpleName());
        return body;
    }
}

In the CustomRequestBodyAdvice class, we're implementing all four methods of RequestBodyAdvice.

Firstly, the supports method decides whether this implementation should run for the current request. To perform this decision, Spring MVC provides some contextual information like the method parameter or target conversion type. In our example, we're checking the controller class and target conversion type.

Then, we have the beforeBodyRead method which runs before Spring MVC reads the request body.

Similarly, the afterBodyRead method runs after the body is read and converted into an Object. In afterBodyRead, we have access to the converted object. Hence we can cast it to an appropriate type and modify its fields. In our case, we're casting it to Question and setting the Date field.

Lastly, when the request body is empty, we can put our custom logic into the handleEmptyBody method. We must return a value to use, either the provided object or an entirely new object. In CustomRequestBodyAdvice, we're returning the provided value without any modification.

4. Invocation Order

Now, let's see in which order Spring MVC calls our RequestBodyAdvice and controller methods.

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class QuestionControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    public void shouldApplyAdvices() throws Exception {
        Question question = new Question();
        question.setQuestionMessage("How is weather?");
        mockMvc.perform(post("/ask")
          .contentType(MediaType.APPLICATION_JSON_VALUE)
          .content(objectMapper.writeValueAsString(question))
          .accept(MediaType.APPLICATION_JSON_VALUE))
          .andExpect(status().isOk());
    }
}

Here, we have a simple test case which calls our /ask endpoint.

When we run the test, the print statements outline the order:

In supports() method of CustomRequestBodyAdvice
In beforeBodyRead() method of CustomRequestBodyAdvice
In supports() method of CustomRequestBodyAdvice
In afterBodyRead() method of CustomRequestBodyAdvice
In controller method

The methods of RequestBodyAdvice run first. Then the controller methods run after Spring MVC calls all other RequestBodyAdvices.

5. Registering RequestBodyAdvice

In order to register the RequestBodyAdvice implementations, we have two ways.

Primarily, we can annotate our implementation with @ControllerAdvice - as we did previously:

@ControllerAdvice
public class CustomRequestBodyAdvice implements RequestBodyAdvice {

    // Implementation
}

Alternatively, we can register a RequestBodyAdvice implementation manually by using the RequestMappingHandlerAdapter class:

@Configuration
public class CustomWebMvcConfiguration extends DelegatingWebMvcConfiguration {

    @Bean
    @Override
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        RequestMappingHandlerAdapter requestMappingHandlerAdapter = super.requestMappingHandlerAdapter();
        
        List<RequestBodyAdvice> additionalAdvices = new ArrayList<>();
        additionalAdvices.add(new CustomRequestBodyAdvice());
        requestMappingHandlerAdapter.setRequestBodyAdvice(additionalAdvices);
        
        return requestMappingHandlerAdapter;
    }
}

Here, we're extending the DelegatingWebMvcConfiguration class and marking it as @Configuration. Then we're creating a RequestMappingHandlerAdapter bean where we're also registering our RequestBodyAdvice implementation.

6. Summary

In this tutorial, we've investigated the RequestBodyAdvice interface to customize the way Spring MVC reads the request body.

Finally, check out the source code for all examples in this tutorial over on Github.