Summary

This request is to improve the documentation around the behavior of @RestControllerAdvice and @ControllerAdvice when both are used in a Spring Boot application. Many developers reasonably expect that:

  • @RestControllerAdvice handles exceptions from @RestController
  • @ControllerAdvice handles exceptions from @Controller (typically MVC controllers returning views)

However, both annotations are functionally equivalent in exception resolution unless explicitly scoped. This causes unexpected results when both advice classes are defined globally.


Actual Behavior

When both @ControllerAdvice and @RestControllerAdvice are defined without scoping, Spring does not distinguish between which type of controller threw the exception. The resolution order depends on internal registration or @Order, meaning:

  • @ControllerAdvice might end up handling exceptions thrown by a @RestController, even though @RestControllerAdvice exists.
  • @RestControllerAdvice might apply to MVC controllers, returning JSON when a view is expected.

This is non-intuitive and can cause confusing results, especially in mixed applications with both REST and MVC endpoints.


Technical Reason

@RestControllerAdvice is simply a specialization of @ControllerAdvice with @ResponseBody added:

@Target(...)
@Retention(...)
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {}

Therefore, both advice classes are global unless explicitly scoped.


Suggested Documentation Improvements

The documentation should include:

  1. A clear explanation that both annotations apply globally by default.
  2. A note that Spring does not route exceptions based on the type of controller (REST vs MVC).
  3. A recommendation to use the annotations attribute of @ControllerAdvice to explicitly scope handlers to the appropriate controller type.

Recommended Solution (Code Example)

This example shows how to correctly separate exception handling for REST and MVC controllers.

1. REST Controller

@RestController
@RequestMapping("/api")
public class ApiController {

    @GetMapping("/fail")
    public String fail() {
        throw new RuntimeException("REST failure");
    }
}

2. MVC Controller

@Controller
public class WebController {

    @GetMapping("/page")
    public String page() {
        throw new RuntimeException("MVC failure");
    }
}

3. REST Exception Handler

@RestControllerAdvice(annotations = RestController.class)
public class GlobalRestExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleRest(Exception ex) {
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body("Handled in REST handler: " + ex.getMessage());
    }
}

4. MVC Exception Handler

@ControllerAdvice(annotations = Controller.class)
public class GlobalMvcExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ModelAndView handleMvc(Exception ex) {
        ModelAndView mv = new ModelAndView("error");
        mv.addObject("message", "Handled in MVC handler: " + ex.getMessage());
        return mv;
    }
}

Expected Output

  • GET /api/fail returns: ```http HTTP/1.1 500 Content-Type: text/plain

Handled in REST handler: REST failure ```

  • GET /page renders error.html with message: html Handled in MVC handler: MVC failure

Why This Matters

This undocumented behavior can lead to hours of debugging for developers who assume the framework will "do the right thing" based on controller types. Proper scoping is easy once known — but right now, it is not obvious or documented in the main Spring Boot or Spring Framework guides.

Clarifying this would save developers significant confusion and help ensure exception handling works predictably in mixed REST/MVC applications.

Comment From: rstoyanchev

Thanks for bringing this up, and apologies for any confusion caused. I can see how the symmetry in the names can lead to this assumption.

That said @RestControllerAdvice is nothing but a @ControllerAdvice that renders to the response body. By default, it applies to any controller. We cannot limit it to just @RestController, which is very much an optional shortcut for @Controller with @ResponseBody. You could for example use the long form. Moreover is possible to configure mappedHandlerClasses or a mappedHandlerPredicate on ExceptionHandlerExceptionResolver to apply further to functional endpoints and other types of handlers.

We can certainly improve the documentation with your experience in mind and address this question specifically. Do you have a specific place in mind in the reference docs or Javadoc where you would have noticed such guidance?

Comment From: Mamun-Al-Babu-Shikder

Thanks for bringing this up, and apologies for any confusion caused. I can see how the symmetry in the names can lead to this assumption.

That said @RestControllerAdvice is nothing but a @ControllerAdvice that renders to the response body. By default, it applies to any controller. We cannot limit it to just @RestController, which is very much an optional shortcut for @Controller with @ResponseBody. You could for example use the long form. Moreover is possible to configure mappedHandlerClasses or a mappedHandlerPredicate on ExceptionHandlerExceptionResolver to apply further to functional endpoints and other types of handlers.

We can certainly improve the documentation with your experience in mind and address this question specifically. Do you have a specific place in mind in the reference docs or Javadoc where you would have noticed such guidance?

Thanks for your valuable comment. Right now i could not recall that specific place.