How to get the @RequestBody in an @ExceptionHandler (Spring REST) How to get the @RequestBody in an @ExceptionHandler (Spring REST) spring spring

How to get the @RequestBody in an @ExceptionHandler (Spring REST)


You can reference the request body object to a request-scoped bean. And then inject that request-scoped bean in your exception handler to retrieve the request body (or other request-context beans that you wish to reference).

// @Component// @Scope("request")@ManagedBean@RequestScopepublic class RequestContext {    // fields, getters, and setters for request-scoped beans}@RestController@RequestMapping("/api/v1/persons")public class PersonController {    @Inject    private RequestContext requestContext;    @Inject    private PersonService personService;    @PostMapping    public Person savePerson(@RequestBody Person person) throws PersonServiceException {         requestContext.setRequestBody(person);         return personService.save(person);    }}@ControllerAdvicepublic class ExceptionMapper {    @Inject    private RequestContext requestContext;    @ExceptionHandler(PersonServiceException.class)    protected ResponseEntity<?> onPersonServiceException(PersonServiceException exception) {         Object requestBody = requestContext.getRequestBody();         // ...         return responseEntity;    }}


Accepted answer creates a new POJO to pass things around, but the same behaviour can be achieved without creating additional objects by reusing the http request.

Example code for Controller mapping:

public ResponseEntity savePerson(@RequestBody Person person, WebRequest webRequest) {    webRequest.setAttribute("person", person, RequestAttributes.SCOPE_REQUEST);

And later in the ExceptionHandler class / method you can use:

@ExceptionHandler(Exception.class)public ResponseEntity exceptionHandling(WebRequest request,Exception thrown) {    Person person = (Person) request.getAttribute("person", RequestAttributes.SCOPE_REQUEST);


You should be able to get the content of the request body by using the RequestBodyAdvice interface. If you implement this on a class annotated with @ControllerAdvice it should be picked up automatically.

To get other request information like the HTTP method and query params I'm using an interceptor. I'm capturing all this request info for error reporting in a ThreadLocal variable which I clear on the afterCompletion hook in that same interceptor.

The class below implements this and can be used in your ExceptionHandler to get all request information:

import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.core.MethodParameter;import org.springframework.http.HttpInputMessage;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.reflect.Type;import java.util.HashMap;import java.util.Map;@ControllerAdvicepublic class RequestInfo extends HandlerInterceptorAdapter implements RequestBodyAdvice {    private static final Logger logger = LoggerFactory.getLogger(RequestInfo.class);    private static final ThreadLocal<RequestInfo> requestInfoThreadLocal = new ThreadLocal<>();    private String method;    private String body;    private String queryString;    private String ip;    private String user;    private String referrer;    private String url;    public static RequestInfo get() {        RequestInfo requestInfo = requestInfoThreadLocal.get();        if (requestInfo == null) {            requestInfo = new RequestInfo();            requestInfoThreadLocal.set(requestInfo);        }        return requestInfo;    }    public Map<String,String> asMap() {        Map<String,String> map = new HashMap<>();        map.put("method", this.method);        map.put("url", this.url);        map.put("queryParams", this.queryString);        map.put("body", this.body);        map.put("ip", this.ip);        map.put("referrer", this.referrer);        map.put("user", this.user);        return map;    }    private void setInfoFromRequest(HttpServletRequest request) {        this.method = request.getMethod();        this.queryString = request.getQueryString();        this.ip = request.getRemoteAddr();        this.referrer = request.getRemoteHost();        this.url = request.getRequestURI();        if (request.getUserPrincipal() != null) {            this.user = request.getUserPrincipal().getName();        }    }    public void setBody(String body) {        this.body = body;    }    private static void setInfoFrom(HttpServletRequest request) {        RequestInfo requestInfo = requestInfoThreadLocal.get();        if (requestInfo == null) {            requestInfo = new RequestInfo();        }        requestInfo.setInfoFromRequest(request);        requestInfoThreadLocal.set(requestInfo);    }    private static void clear() {        requestInfoThreadLocal.remove();    }    private static void setBodyInThreadLocal(String body) {        RequestInfo requestInfo = get();        requestInfo.setBody(body);        setRequestInfo(requestInfo);    }    private static void setRequestInfo(RequestInfo requestInfo) {        requestInfoThreadLocal.set(requestInfo);    }    // Implementation of HandlerInterceptorAdapter to capture the request info (except body) and be able to add it to the report in case of an error    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {        RequestInfo.setInfoFrom(request);        return true;    }    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) {        RequestInfo.clear();    }    // Implementation of RequestBodyAdvice to capture the request body and be able to add it to the report in case of an error    @Override    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {        return true;    }    @Override    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {        return inputMessage;    }    @Override    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {        RequestInfo.setBodyInThreadLocal(body.toString());        return body;    }    @Override    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {        return body;    }}