Error Handling for REST with Spring
Before Spring 3.2 the two main approaches to handle exceptions where HandleExceptionResolver or @ExceptionHandler annotation. The it was added @ControllerAdvice and ResponseStatusException.
Controller level (MVC)
This strategy only works for active controllers is not global to the entire application.
@ExceptionHandler({Exception.class,Exception1.class})
public void handleException(){
//handle
}
HandleExceptionResolver
This is enabled by default in DispatchServlet.
DefaultHandlerExceptionResolver
Used to resolve Spring exceptions to their corresponding HTTP Status codes
Handling Standard Spring MVC Exceptions
ResponseStatusExceptionResolver
Uses @ResponseStatus annotation on custom exceptions to map them into HTTP Status codes.
However this does not allow to set body content.
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class MyResourceNotFoundException extends RuntimeException {
public MyResourceNotFoundException() {
super();
}
public MyResourceNotFoundException(String message, Throwable cause) {
super(message, cause);
}
public MyResourceNotFoundException(String message) {
super(message);
}
public MyResourceNotFoundException(Throwable cause) {
super(cause);
}
}
Custom HadlerExceptionResolver
Creates an extension of AbstractHandlerExceptionResolver that allows to set the body content.
@Component
public class RestResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver {
@Override
protected ModelAndView doResolveException(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
try {
if (ex instanceof IllegalArgumentException) {
return handleIllegalArgument(
(IllegalArgumentException) ex, response, handler);
}
...
} catch (Exception handlerException) {
logger.warn("Handling of [" + ex.getClass().getName() + "]
resulted in Exception", handlerException);
}
return null;
}
private ModelAndView
handleIllegalArgument(IllegalArgumentException ex, HttpServletResponse response)
throws IOException {
response.sendError(HttpServletResponse.SC_CONFLICT);
String accept = request.getHeader(HttpHeaders.ACCEPT);
...
return new ModelAndView();
}
}
ControllerAdvice and ExceptionHandler
More suitable for Rest, this solution breaks from the MVC model allows to return an ResponseEntity object using @ExceptionHandler annotation. @ControllerAdvice allows to set the handler as common to all controllers and consolidate our multiple, scattered @ExceptionHandlers from before into a single, global error handling component.
@ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
@ExceptionHandler(value
= { IllegalArgumentException.class, IllegalStateException.class })
protected ResponseEntity<Object> handleConflict(
RuntimeException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.CONFLICT, request);
}
}
ResponseStatusException
Allow to throw an exception of type ResponseStatusException with an Http status and optional reason and clause, a more flexible but less methodical approach.
@GetMapping(value = "/{id}")
public Foo findById(@PathVariable("id") Long id, HttpServletResponse response) {
try {
Foo resourceById = RestPreconditions.checkFound(service.findOne(id));
eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response));
return resourceById;
}
catch (MyResourceNotFoundException exc) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Foo Not Found", exc);
}
}