0

I have 2 exception handler classes annotated with @RestControllerAdvice and:

I use the first one as global exception handler to catch exceptions:

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
    protected ResponseEntity<Object> handleMethodArgumentNotValid(...) {
      // ...
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseEntity<Object> handleAllUncaughtException(Exception ex, WebRequest request) {
        // ...
    }

    // code omitted for clarity
}

and the second for validation exceptions (I creates custom validation):

@RestControllerAdvice
public class ValidationExceptionHandler { // did not extend from extends ResponseEntityExceptionHandler

    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
    protected ValidationErrorResponse onConstraintValidationException(ConstraintViolationException e) {
        // ...
    }
}

When I move onConstraintValidationException to GlobalExceptionHandler class, I catch validation exception and display corresponding message. But when it is in the second ControllerAdvice class (ValidationExceptionHandler) as shown above, code does not hit onConstraintValidationException method.

I also tried to extend the second class from ResponseEntityExceptionHandler, but does not make any sense.

So, what is the problem and how can I fix it?

  • 1
    What is your version of Spring Boot and/or Spring Framework? Post full class (include import). – Vy Do Jul 24 '22 at 11:03
  • Have you annotated your Controller with @Validated annotation to enable spring to validate controller? – acearch Jul 24 '22 at 12:16
  • @JamesGraham v2.7.1, but the exception is working when it is defined in `GlobalExceptionHandler`. For this reason, it does not related to version. Maybe completely related to `@Order()` annotation. Any idea? –  Jul 24 '22 at 21:37
  • @acearch Good point, but I had already annotated that. Otherwise it does not work in both exception handling classes. So, it can be fixed using `@Order()`, but I am not sure if there is a more proper way. Any idea? –  Jul 24 '22 at 21:40

1 Answers1

1

In your case it could be question of priorities... The first exception handler has highest priority, and probably the handleAllUncaughtException is going to capture all the exceptions.

For catching the ConstraintViolationException in the second handler (ValidationExceptionHandler) you should give to it the highest priority, like so:

@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ValidationExceptionHandler { 

and to the first one lowest:

@RestControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

Can have a look at this discussion, it can be interesting to learn more about how it works and have some alternative solutions: Setting Precedence of Multiple @ControllerAdvice @ExceptionHandlers

Edit: in case you need something more flexible or create a more complex hierarchy of handlers, you can use simple integers in the Order annotation. Lowest values, highest priority. So something like this can work as well:

@RestControllerAdvice
@Order(-1)
public class ValidationExceptionHandler { 
//...

@RestControllerAdvice
@Order(0)
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
//...

In the Ordered source code you have:

public interface Ordered {

/**
 * Useful constant for the highest precedence value.
 * @see java.lang.Integer#MIN_VALUE
 */
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

/**
 * Useful constant for the lowest precedence value.
 * @see java.lang.Integer#MAX_VALUE
 */
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

In the Order annotation you can see the default is LOWEST_PRECEDENCE (Integer.MAX_VALUE):

public @interface Order {

/**
 * The order value.
 * <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.
 * @see Ordered#getOrder()
 */
    int value() default Ordered.LOWEST_PRECEDENCE;

}
cdr89
  • 958
  • 9
  • 18
  • You rock! Voted up ;) Thanks a lot, `@Order` annotation fixed the problem. However, I encounter some weird situations. Do you have any suggestions for these points? >>> –  Jul 24 '22 at 21:23
  • **1.** the `@Order(Ordered.LOWEST_PRECEDENCE)` annotation gives "redundant parameter" warning and I used it as `@Order()`, but not sure if I have to set using an int e.g. `@Order(Integer.MAX_VALUE)` (for lowest pritority). –  Jul 24 '22 at 21:26
  • **2.** On the other hand, I thought that setting only lowest precedence would be enough for my situation. And used the `Order()` annotation for only `GlobalExceptionHandler`, but it does not work. So, do I have to use that annotation for `ValidationExceptionHandler` as well? –  Jul 24 '22 at 21:28
  • **3.** Should I extend `ResponseEntityExceptionHandler` for `ValidationExceptionHandler` also (as I extend in `GlobalExceptionHandler`)? –  Jul 24 '22 at 21:31
  • @hans **#1**, is fine to use without parameter, since the default is lowest precedence. **#2**, since is the default the lowest, is sufficient to annotate the ValidationExceptionHandler with the highest precedence. **#3**, is not needed, since there are some default handling for exception and these are already in the GlobalExceptionHandler – cdr89 Jul 25 '22 at 13:57
  • @hans edited my answer adding some more details about Order and Ordered – cdr89 Jul 25 '22 at 14:09