33

The Question

When creating a controller in Spring Boot to handle all errors/exceptions in a custom way, including custom exceptions, which technique should be preferred?

  1. Should the controller implement the Spring Boot's ErrorController?

  2. Should the controller extend Spring's ResponseEntityExceptionHandler?

  3. Both: a single controller implementing and extending both classes including both of their functionality?

  4. Both: two separate controllers, one implementing ErrorController, the other extending ResponseEntityExceptionHandler?

The Goal

The reason for this post is to find a way of exception-handling in Spring Boot with all of the following attributes:

  • All Throwables occurring in controllers/filters/interceptors during the handling of a request should be caught.
  • In the case of catching a Throwable, we do not want to expose any of the stack trace or other implementation details to the client ever (unless explicitly coded that way).
  • It should be possible to handle all occurred Throwables separately by their class. For any other non-specified type, a default response can be specified. (I know for sure, that this is possible with @ExceptionHandler. But ErrorController?)
  • The code should be as clean and explicit as possible with no ugly work-arounds or UB to achieve this goal.

More Details

I noticed that both the controllers (see 1 and 2 above) may contain methods returning a ResponseEntity object, thus handling the occurred exception and returning a response to the client. So they could in theory produce the same result?

There are several tutorials out there on the use of techniques 1 and 2. But I have found no articles considering both options, comparing them or using them together, which raises several additional questions:

  1. Should they even be considered together?

  2. What are the main differences between these two proposed techniques? What are the similarities?

  3. Is one a more powerful version of the other? Is there something one can do that the other can't and vice versa?

  4. Can they be used together? Are there situations where this would be necessary?

  5. If they are used together, how would an exception be handled? Does it go through both handlers or just one? In the case of the latter, which one?

  6. If they are used together, and an exception is thrown inside the controller (one or the other) during exception handling, how would that exception be handled? Is it sent to the other controller? Could exceptions theoretically start bouncing between controllers or create some other kind of non-recovering loop?

  7. Is there any trusted/official documentation on how Spring Boot uses Spring's ResponseEntityExceptionHandler internally or how it expects it to be used in Spring Boot applications?

  8. If ResponseEntityExceptionHandler alone is already enough, then why does ErrorController exist?

When looking at the Spring's ResponseEntityExceptionHandler together with the @ExceptionHandler annotation, it seems to be more powerful in handling different types of exceptions separately and using cleaner code. But because Spring Boot is built on top of Spring, does this mean:

  • When using Spring Boot, we should implement ErrorController instead of extending ResponseEntityExceptionHandler?
  • Can ErrorController do everything ResponseEntityExceptionHandler can, including handling different types of exceptions separately?

EDIT: Related: Spring @ControllerAdvice vs ErrorController

Snackoverflow
  • 5,332
  • 7
  • 39
  • 69

2 Answers2

47

Spring Boot application has a default configuration for error handling - ErrorMvcAutoConfiguration.

What it basically does, if no additional configuration provided:

  • it creates default global error controller - BasicErrorController
  • it creates default 'error' static view 'Whitelabel Error Page'.

BasicErrorController is wired to '/error' by default. If there is no customized 'error' view in the application, in case of an exception thrown from any controller, the user lands to /error whitelabel page, filled with information by BasicErrorController.

If application has a controller implementing ErrorController it replaces BasicErrorController.

If any exception occurs in error handling controller, it will go through Spring exception filter (see more details down below) and finally if nothing is found this exception will be handled by the underlying application container, e.g. Tomcat. The underlying container will handle the exception and show some error page/message depending on its implementation.

There is an interesting piece of information in BasicErrorController javadoc:

Basic global error Controller, rendering ErrorAttributes. More specific errors can be handled either using Spring MVC abstractions (e.g. @ExceptionHandler) or by adding servlet server error pages.

BasicErrorController or ErrorController implementation is a global error handler. It can be used in conjunction with @ExceptionHandler.

Here we come to ResponseEntityExceptionHandler

A convenient base class for @ControllerAdvice classes that wish to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods. This base class provides an @ExceptionHandler method for handling internal Spring MVC exceptions.

In other words, that means that ResponseEntityExceptionHandler is just a convenience class, which already contains Spring MVC exception handling. And we can use it as a base class for our custom class to handle controllers' exceptions. For our custom class to work, it must be annotated with @ControllerAdvice.

Classes annotated with @ControllerAdvice can be used at the same time as the global error handler (BasicErrorController or ErrorController implementation). If our @ControllerAdvice annotated class (which can/or not extend ResponseEntityExceptionHandler) doesn't handle some exception, the exception goes to the global error handler.

So far we looked at ErrorHandler controller and anything annotated with @ControllerAdvice. But it is much more complicated. I found a really valuable insight in the question - Setting Precedence of Multiple @ControllerAdvice @ExceptionHandlers.

Edit:

To keep it simple:

  1. First Spring searches for an exception handler (a method annotated with @ExceptionHandler) within @ControllerAdvice classes. See ExceptionHandlerExceptionResolver.
  2. Then it checks if the thrown exception is annotated with @ResponseStatus or derives from ResponseStatusException. See ResponseStatusExceptionResolver.
  3. Then it goes through Spring MVC exceptions' default handlers. See DefaultHandlerExceptionResolver.
  4. And at the end if nothing is found, the control is forwarded to the error page view with the global error handler behind it. This step is not executed if the exception comes from the error handler itself.
  5. If no error view is found (e.g. global error handler is disabled) or step 4 is skipped, then the exception is handled by the container.
da-sha1
  • 706
  • 5
  • 7
  • Does this mean that when I extend the `ResponseEntityExceptionHandler` and override some of its methods, *my* overridden methods will automatically be used for Spring MVC exception handling? Like.. if I extend this class, does Spring detect it and not use their default class? – Snackoverflow Mar 15 '19 at 16:18
  • I have a custom implementation of `ErrorController` and an `@ExceptionHandler` for a custom exception type. If that custom exception type is thrown inside any of my controllers, **it gets sent to `ErrorController` by default and the `@ExceptionHandler` is ignored**. Only if the `ErrorController` throws it further, then it is sent to the `@ExceptionHandler`. This behavior is the opposite of what you described, how come? – Snackoverflow Mar 15 '19 at 19:59
  • 1
    The sequence is this: (1) normal controller throws -> (2) my `ErrorController` catches and throws -> (3) `DispatcherServlet` sends it to `AbstractHandlerExceptionResolver` which sends it to my `@ExceptionHandler` -> (4) my `@ExceptionHandler` catches it and resolves it. – Snackoverflow Mar 15 '19 at 20:05
  • @anddero thank you, I found an error in my reply related to DefaultHandlerExceptionResolver execution order. The sequece you describe makes me tink that the exception which led to `ErrorController` is not handeled by any of your methods with @ExceptionHandler. That's why the first chain of exception resolver search led you directly to your `ErrorController`. Please add a breakpoint at HandlerExceptionResolverComposite.resolveException to see how many times it stops there. – da-sha1 Mar 16 '19 at 00:16
  • 4
    Okay, I think I get it now. The `@ExceptionHandler` only catches the exceptions if they are thrown from the controllers. But in one case, my `javax.servlet.Filter` implementation throws an exception during pre-processing. And in this case, it is immediately mapped to `/error` and sent to my `ErrorController` implementation instead. But because the `ErrorController` is a controller, if it throws the exception further, it can be handled by the `@ExceptionHandler`. – Snackoverflow Mar 16 '19 at 10:48
  • You have pretty much answered all of the questions. However, I am still looking for one little bit of detail: **Can an `ErrorController` implementation also check the exception type to handle different exceptions in a different way?** If I find an answer to this, I will award you the bounty because you have given me exactly the stuff I was looking for :) – Snackoverflow Mar 16 '19 at 10:58
  • @anddero Once in `ErrorController` , `HttpServletRequest` has an attribute set which you can use to retrieve the exception : `Throwable ex = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION)`. The original exception would be probably wrapped inside [NestedServletException](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/NestedServletException.html) – da-sha1 Mar 16 '19 at 19:52
2

I will admit that I am not overly familiar with Spring's ErrorController, but looking at your specified goals I believe all of them can be achieved cleanly using Spring's @ControllerAdvice. The following is an example how I have been using it in my own applications:

@ControllerAdvice
public class ExceptionControllerAdvice {

    private static final String INCOMING_REQUEST_FAILED = "Incoming request failed:";
    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionControllerAdvice.class);
    private final MessageSource source;

    public ExceptionControllerAdvice2(final MessageSource messageSource) {
        source = messageSource;
    }

    @ExceptionHandler(value = {CustomException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorMessage badRequest(final CustomException ex) {
        LOGGER.error(INCOMING_REQUEST_FAILED, ex);
        final String message = source.getMessage("exception.BAD_REQUEST", null, LocaleContextHolder.getLocale());
        return new ErrorMessage(HttpStatus.BAD_REQUEST.value(), message);
    }

    @ExceptionHandler(Throwable.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ErrorMessage internalServerError(final Exception ex) {
        LOGGER.error(INCOMING_REQUEST_FAILED, ex);
        final String message =
                source.getMessage("exception.INTERNAL_SERVER_ERROR", null, LocaleContextHolder.getLocale());
        return new ErrorMessage(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
    }
}
Zealot
  • 51
  • 4
  • 1
    Thanks, it seems very *by-the-book* approach using only the `@ExceptionHandler`s. I can understand and appreciate the cleanliness of this proposal, but I am more interested in finding out why Spring Boot decided to introduce an `ErrorController` and why it is being promoted in their tutorials very often. My guess is, it is another way of making their framework more abstract and more easily adoptable on top of Spring, without much coding and understanding of the underlying implementation. I am suggesting this based on Spring Boot's goals as a framework and the above answer from **da-sha1**. – Snackoverflow Mar 15 '19 at 16:27