How can one customize the message field in the error response body (and at the same time customize the http status code)?

Based on the answer to #1677 I attempted to use the sendError(int, String) method to customize both the http status code and the message field simultaneously:

@ExceptionHandler(IllegalArgumentException.class)
void handleIllegalArgumentException(HttpServletResponse response) throws IOException {
    response.sendError(HttpStatus.BAD_REQUEST.value(), "custom message");
}

When a request causes an IllegalArgumentException the client will get the following response:

HTTP Status 400 - Bad Request
 {
    "timestamp": 1413702719532,
    "status": 400,
    "error": "Bad Request",
    "exception": "java.lang.IllegalArgumentException",
    "message": "original exception message",
    "path": "/test"
}

The problem is that I expected the message field to equal custom message (or whatever the value was passed to message parameter to the sendError(int, java.lang.String) method). Instead, I see original exception message (i.e. the value that was passed to the IllegalArgumentException constructor).

Side note, the http status header and all other fields in the response body are correct.

Related to #1677 Spring Boot version 1.1.8

Comment From: wilkinsona

You can hide the exception from DefaultErrorAttributes by clearing a request attribute:

@ExceptionHandler(IllegalArgumentException.class)
void handleIllegalArgumentException(HttpServletRequest request, HttpServletResponse response) throws IOException {
    request.setAttribute(DefaultErrorAttributes.class.getName() + ".ERROR", null);
    response.sendError(HttpStatus.BAD_REQUEST.value(), "custom message");
}

If you do this you'll also lose the exception entry in the response. You should consider this a workaround at the moment. The attribute name is really an implementation detail that might change. We can use this issue to explore the possibility of doing something more official.

Comment From: matsev

Ok. I am also considering another workaround for now, i.e. wrapping the method call in try block and throw a new exception with the desired message in the catch block. It is not pretty, especially not if the same exception needs to be caught in several places.

Comment From: wilkinsona

I neglected to mention earlier that you can provide your own ErrorAttributes bean and have complete control over the contents of the JSON. You could subclass DefaultErrorAttributes to make the custom message set via sendError take precedence:

    @Bean
    public ErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes() {

            @Override
            public Map<String, Object> getErrorAttributes(
                    RequestAttributes requestAttributes,
                    boolean includeStackTrace) {
                Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
                Object errorMessage = requestAttributes.getAttribute(RequestDispatcher.ERROR_MESSAGE, RequestAttributes.SCOPE_REQUEST);
                if (errorMessage != null) {
                    errorAttributes.put("message",  errorMessage);
                }
                return errorAttributes;
            }

        };
    }

Comment From: dsyer

With that change in #1762 sendError(int,String) should work as expected. I believe the same thing should apply with @ResponseStatus on a method or an Exception (which is better than having to inject HttpServletResponse). The method has to be a void return type I think.

Comment From: singhalavi

i spent couple of hours sorting it out, thought of sharing it for others benefit.

you can do it this way

@ExceptionHandler(ServiceException.class)
    @ResponseBody
    public ErrorResponse handleServiceException(HttpServletRequest req, HttpServletResponse response, ServiceException e) 
    {
        ErrorResponse error = new ErrorResponse();
        error.setResponseMessage(e.getMessage());
        //Set custom non standard http status code
        response.setStatus(499);
        return error;
    }

ErrorResponse is a normal java object

Comment From: Kartikkman

i spent couple of hours sorting it out, thought of sharing it for others benefit.

you can do it this way

@ExceptionHandler(ServiceException.class) @ResponseBody public ErrorResponse handleServiceException(HttpServletRequest req, HttpServletResponse response, ServiceException e) { ErrorResponse error = new ErrorResponse(); error.setResponseMessage(e.getMessage()); //Set custom non standard http status code response.setStatus(499); return error; }

ErrorResponse is a normal java object

Hi @singhalavi , It worked Thanks a lot man !! Could you please also share the reason behind it working !!

Comment From: RajHirani

You can also use @ControllerAdvice @matsev

@ControllerAdvice
@Slf4j
public class ExceptionHelper {

    @ExceptionHandler(value = { UserAlreadyExistAuthenticationException.class })
    public ResponseEntity handleInvalidInputException(UserAlreadyExistAuthenticationException ex) {
        log.error("UserAlreadyExistAuthenticationException: ",ex.getMessage());
        return new ResponseEntity<Object>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
}