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"
}
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"
}
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.