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:
- 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
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
renderserror.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 configuremappedHandlerClasses
or amappedHandlerPredicate
onExceptionHandlerExceptionResolver
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.