Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

350
Views
Spring: Different exception handler for RestController and Controller

In Spring you can set up "global" exception handler via @ControllerAdvice and @ExceptionHandler annotation. I'm trying to utilize this mechanism to have two global exception handlers:

  • RestControllerExceptionHandler - which should return error responses as json for any controller annotated with @RestController
  • ControllerExceptionHandler - which should print error message to the screen for any other controller (annottated with @Controller)

The problem is that when I declare these two exception handlers spring always uses the ControllerExceptionHandler and never RestControllerExceptionHandler to handle the exception.

How to make this work ? BTW: I tried to use @Order annotation but this does not seem to work.

Here are my exception handlers:

// should handle all exception for classes annotated with         
@ControllerAdvice(annotations = RestController.class)
public class RestControllerExceptionHandler {


  @ExceptionHandler(Exception.class)
  public ResponseEntity<ErrorResponse> handleUnexpectedException(Exception e) {

    // below object should be serialized to json
    ErrorResponse errorResponse = new ErrorResponse("asdasd"); 

    return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
  }

// should handle exceptions for all the other controllers
@ControllerAdvice(annotations = Controller.class)
public class ControllerExceptionHandler {


  @ExceptionHandler(Exception.class)
  public ResponseEntity<String> handleUnexpectedException(Exception e) {
    return new ResponseEntity<String>("Unexpected exception, HttpStatus.INTERNAL_SERVER_ERROR);
  }


}
}

When I remove ControllerExceptionHandler than RestControllerExceptionHandler is correctly called by spring (only for classes annotated with @RestController).... but when I add ControllerExceptionHandler than all goes via ControllerExceptionHandler. Why?

about 4 years ago · Santiago Trujillo
3 answers
Answer question

0

After some deeper investigation it seems that the alphabetical order matters :/.

When I renamed my RestControllerExceptionHandler to ARestControllerExceptionHandler (which alphabetically precedes ControllerExceptionHandler) all works as expected! ARestControllerExceptionHandler correctly handles exceptions from RestControllers and ControllerExceptionHandler handles exception from other controllers.

I created a bug in spring for this: https://jira.spring.io/browse/SPR-15432

--- EDIT:

I received the answer for SPR-15432 where it is suggested that this case can be solved with @Order (org.springframework.core.annotation.Order) annotation or by implementing Ordered interface.

This did not work for me before, but it seems that I have imported wrong @Order annotation. (from log4j2 instead of spring). After fixing this it works. Fixed version is following:

// should handle all exception for classes annotated with         
@ControllerAdvice(annotations = RestController.class)
@Order(1) // NOTE: order 1 here
public class RestControllerExceptionHandler {


  @ExceptionHandler(Exception.class)
  public ResponseEntity<ErrorResponse> handleUnexpectedException(Exception e) {

    // below object should be serialized to json
    ErrorResponse errorResponse = new ErrorResponse("asdasd"); 

    return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
  }
}
// should handle exceptions for all the other controllers
@ControllerAdvice(annotations = Controller.class)
@Order(2)  // NOTE: order 2 here
public class ControllerExceptionHandler {


  @ExceptionHandler(Exception.class)
  public ResponseEntity<String> handleUnexpectedException(Exception e) {
    return new ResponseEntity<String>("Unexpected exception, HttpStatus.INTERNAL_SERVER_ERROR);
  }
}
about 4 years ago · Santiago Trujillo Report

0

That's happening because @RestController annotation is itself an @Controller as well so Spring is considering the @ControllerAdvice with annotation = Controller.class for both.

You may try another method to define the subset of Controllers that the @ControllerAdvice will have effect, so here are some solutions:


Solution 1

  1. Create a new Annotation @NotRestController:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
        public @interface NotRestController {
    }
    
  2. Mark controllers that are not @RestController with both @Controller and @NotRestController:

    @Controller
    @NotRestController
    @RequestMapping("/controller")
    public class SampleController {
    }
    
  3. Use NotRestController.class on ControllerExceptionHandler:

    @ControllerAdvice(annotations = NotRestController.class)
    public class ControllerExceptionHandler {
    

I've created a sample project with Solution 1 and shared it on Github:

https://github.com/brunocleite/springexceptionhandlertest


Solution 2

  1. Move your @RestController classes into a package foo.bar and your @Controller classes into another package foo.nuk.

  2. Use basePackages property of @ControllerAdvice instead of annotations:

    @ControllerAdvice(basePackages={"foo.bar"})
    public class RestControllerExceptionHandler {
    
    @ControllerAdvice(basePackages={"foo.nuk"})
    public class ControllerExceptionHandler {
    

Best regards!

about 4 years ago · Santiago Trujillo Report

0

To further build on the solution 1 of @bruno-leite:

You can define a meta-annotation so you only need to 1 annotation each time insted of 2:

import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Controller;

import java.lang.annotation.*;

/**
 * Alias annotation for @Controller so that @ControllerAdvice instances can
 * target the web controllers separately from the rest controllers.
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
public @interface WebController {
    @AliasFor(
            annotation = Controller.class
    )
    String value() default "";
}

Use it on the web controller(s):

@WebController
@RequestMapping("/customers")
public class CustomerController {

Finally define a @ControllerAdvice that only targets the web controllers like this:

@ControllerAdvice(annotations = WebController.class)
public class GlobalControllerAdvice {

    @ModelAttribute("displayName")
    public String getDisplayName(@AuthenticationPrincipal KeycloakAuthenticationToken principal) {
        if (principal != null) {
            String givenName = principal.getAccount().getKeycloakSecurityContext().getToken().getGivenName();
            String familyName = principal.getAccount().getKeycloakSecurityContext().getToken().getFamilyName();
            return givenName + " " + familyName;
        } else {
            return null;
        }
    }
}

(This example ensures that my Thymeleaf templates always have the displayName attribute, but can be easily adjusted for the exception handlers as well)

about 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!