21

How can I customize the response status code and the data in the response body if an exception occurs in a Spring Boot web application?

I have created a web app that throws a custom exception if something unexpected occurs due to some bad internal state. Consequently, the response body of the request that triggered the error looks something like:

HTTP/1.1 500 Internal Server Error
{
    "timestamp": 1412685688268,
    "status": 500,
    "error": "Internal Server Error",
    "exception": "com.example.CustomException",
    "message": null,
    "path": "/example"
}

Now, I would like to change the status code and set the fields in the response body. One solution that crossed my mind was something like:

@ControllerAdvice
class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    ErrorMessage handleBadCredentials(CustomException e) {
        return new ErrorMessage("Bad things happened");
    }
}

@XmlRootElement
public class ErrorMessage(
    private String error;

    public ErrorMessage() {
    }

    public ErrorMessage(String error) {
        this.error = error;
    }

    public String getError() {
        return error;
    }

    public void setError(String error) {
        this.error = error;
    }
)

However, that created (as suspected) a completely different response:

HTTP/1.1 400 Bad Request
{
    "error": "Bad things happened"
}
matsev
  • 32,104
  • 16
  • 121
  • 156
  • @zeroflagL Preferably, I would like to customize the response generated by Spring Boot (if possible). Implementing a complete custom solution (like the one provided in the question) works, but is less reusable between different projects. – matsev Oct 07 '14 at 13:44
  • 2
    It's completely up to you if the custom solution is reusable or not. FWIW: The resposne body is assembled by `DefaultErrorAttributes#getErrorAttributes`. You could inject that class into your `CustomResponseEntityExceptionHandler`. – a better oliver Oct 07 '14 at 14:05
  • @zeroflagL I failed to get your suggestion to work (scroll down in the [issue](https://github.com/spring-projects/spring-boot/issues/1677) I filed). However, I did get help in finding a solution, see my answer below (or read my [blog post](http://www.jayway.com/2014/10/19/spring-boot-error-responses/)). – matsev Oct 19 '14 at 10:15

2 Answers2

32

As @zeroflagL mentioned, Spring Boot fabricates the "standard" error response body in org.springframework.boot.autoconfigure.web.DefaultErrorAttributes. Similar to your needs, I wanted to leverage all of that, but simply augment one more "type" field that was provided by some of my exceptions.

I did that by implementing a Component that sub-classed DefaultErrorAttributes. Spring Boot automatically picked it up and used mine instead of the default.

@Component
public class ExtendedErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, 
                                                  boolean includeStackTrace) {
        final Map<String, Object> errorAttributes = 
            super.getErrorAttributes(requestAttributes, 
                                     includeStackTrace);

        final Throwable error = super.getError(requestAttributes);
        if (error instanceof TypeProvider) {
            final TypeProvider typeProvider = (TypeProvider) error;
            errorAttributes.put("type", typeProvider.getTypeIdentifier());
        }

        return errorAttributes;
    }
}

With that, I get an augmented JSON response body, such as

{
  "timestamp": 1488058582764,
  "status": 429,
  "error": "Too Many Requests",
  "exception": "com.example.ExternalRateLimitException",
  "message": "DAILY_LIMIT: too many requests",
  "path": "/api/lookup",
  "type": "DAILY_LIMIT"
}
Oliver
  • 3,815
  • 8
  • 35
  • 63
itzg
  • 1,081
  • 2
  • 13
  • 11
  • Great answer! Can also easily be modified to remove the 'exception' attribute from the error response in production deployments, if you aren't interested in leaking those kind of details. – Andrew Aug 12 '17 at 20:34
  • 9
    As of now (and probably since Spring Boot 2) you need to extend org.springframework.boot.web.servlet.error.DefaultErrorAttributes which has a org.springframework.web.context.request.WebRequest in the parameters instead of RequestAttributes. – Sebastiaan van den Broek Dec 24 '18 at 10:46
  • 1
    Also, since spring boot 2 `getErrorAttributes(WebRequest webRequest, boolean includeStackTrace)` is deprecated. use `getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions errorAttributeOptions)` – Smile Nov 03 '20 at 03:44
11

The http response status code can be changed by using the HttpServletResponse.sendError(int) method, e.g.

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

Alternatively, you can declare the exception type in the @ExceptionHandler annotation if you have two or more exceptions to generate the same response status:

@ExceptionHandler({IllegalArgumentException.class, NullPointerException.class})
void handleBadRequests(HttpServletResponse response) throws IOException {
    response.sendError(HttpStatus.BAD_REQUEST.value());
}

More information can be found in my blog post.

matsev
  • 32,104
  • 16
  • 121
  • 156
  • 2
    It is worth noting that this sets _only_ the response status code; the rest of the response message stays the same. While is is not quite what the asker was asking, it is exactly what I needed for my own use case. – M. Justin Apr 25 '20 at 05:51