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:
@RestControllerAdvicehandles exceptions from@RestController@ControllerAdvicehandles 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:
@ControllerAdvicemight end up handling exceptions thrown by a@RestController, even though@RestControllerAdviceexists.@RestControllerAdvicemight 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:
- A clear explanation that both annotations apply globally by default.
- A note that Spring does not route exceptions based on the type of controller (REST vs MVC).
- A recommendation to use the
annotationsattribute of@ControllerAdviceto 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/failreturns: ```http HTTP/1.1 500 Content-Type: text/plain
Handled in REST handler: REST failure ```
GET /pagerenderserror.htmlwith 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
@RestControllerAdviceis nothing but a@ControllerAdvicethat 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@Controllerwith@ResponseBody. You could for example use the long form. Moreover is possible to configuremappedHandlerClassesor amappedHandlerPredicateonExceptionHandlerExceptionResolverto 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.