Home > other >  How to make sure that @ExceptionHandler(Exception.class) will be called last in Spring Boot?
How to make sure that @ExceptionHandler(Exception.class) will be called last in Spring Boot?

Time:10-25

I have a controller handling some requests:

@RequestMapping(method = RequestMethod.POST, value = "/endpoint")
public ResponseEntity<MyResponse> myEndpoint(@RequestBody MyRequest request) throws Exception {
    //...
}

Such controller may throw several exceptions and for this I'm using @ExceptionHandler this way:

@ExceptionHandler(SomeSpecificException.class)
@ResponseBody
public ResponseEntity<Error> handleSomeSpeficicFailure(SomeSpecificException e) {
    //handle specific exception
}

@ExceptionHandler(SomeOtherSpecificException.class)
@ResponseBody
public ResponseEntity<Error> handleSomeOtherSpeficicFailure(SomeOtherSpecificException e) {
    //handle specific exception
}

//etc.

If the exception that is thrown does not belong to any of the known classes, I've added a generic Exception.class handler which returns a custom 500:

@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<Error> handleUnknownFailure(Exception e) {
    //handle unknown exception
}

Doing some tests, it seems to work fine. If I throw a specific exception I'm called on the specific handler, and if I throw an unmapped exception I'm called on the generic handler.

However I don't see any mention (neither in the JavaDoc nor in the Spring documentation) about a guarantee that I will be called on the specific methods first and on the generic method then.

If Spring was testing that a specific exception is instanceof Exception, it would be true so it may even be calling me on this handler first without checking the other ones.

My questions are:

  • Does anyone know if it's good practice to add a @ExceptionHandler(Exception.class) to handle generic exceptions? If not, what's the right way to do it?
  • How can I have the guarantee that classes hierarchy will be respected (for example if one day I have SomeVerySpecificException extends SomeSpecificException, how will Spring know that it has to call me on the SomeSpecificException - direct parent - before Exception - grandparent)?

CodePudding user response:

After debugging, I've found the answer in their code - even though it's not documented, that's a pity.

@Nullable
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
    List<Class<? extends Throwable>> matches = new ArrayList<>();
    for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
        if (mappedException.isAssignableFrom(exceptionType)) {
            matches.add(mappedException);
        }
    }
    if (!matches.isEmpty()) {
        matches.sort(new ExceptionDepthComparator(exceptionType));
        return this.mappedMethods.get(matches.get(0));
    }
    else {
        return null;
    }
}

So basically for a given Exception, they first look for all those mapped methods for which mappedException.isAssignableFrom(exceptionType).

Once this list is created, then:

  • If the list is empty, they return null and let the default error handling
  • If contains only one element, they return it
  • If contains more than one element, they sort them by depth (from the closest to the farest extend) and return the first method.

So the guarantee is not given by contract but it is in the implementation, and it seems really well done.

  • Related