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);
    }
}