@ControllerAdvice vs @RestControllerAdvice: The Key Difference
I was building a REST API in Spring Boot when I encountered two similar-looking annotations: @ControllerAdvice and @RestControllerAdvice. Which one should I use? The answer turned out to be simpler than I expected.
The Short Answer
@RestControllerAdvice = @ControllerAdvice + @ResponseBody
That’s it. The only difference is that @RestControllerAdvice automatically adds @ResponseBody to all handler methods.
Let me show you what this means in practice.
The Problem
When building a REST API, I needed a global exception handler. I wrote this:
@ControllerAdvicepublic class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class) public ErrorResponse handleResourceNotFound(ResourceNotFoundException ex) { return new ErrorResponse("NOT_FOUND", ex.getMessage()); }}When an exception occurred, I expected JSON back. Instead, I got this error:
javax.servlet.ServletException: Could not resolve view with name '...'Spring was trying to find a view template to render my ErrorResponse object. But I wanted JSON, not an HTML page.
The Solution
I had two options.
Option 1: Add @ResponseBody Manually
@ControllerAdvicepublic class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class) @ResponseBody // <-- Add this to each method public ErrorResponse handleResourceNotFound(ResourceNotFoundException ex) { return new ErrorResponse("NOT_FOUND", ex.getMessage()); }}This works, but I had to remember to add @ResponseBody to every handler method.
Option 2: Use @RestControllerAdvice
@RestControllerAdvice // <-- This includes @ResponseBody automaticallypublic class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class) public ErrorResponse handleResourceNotFound(ResourceNotFoundException ex) { return new ErrorResponse("NOT_FOUND", ex.getMessage()); }}Cleaner. No @ResponseBody annotations needed. The return value is written directly to the HTTP response body as JSON (assuming Jackson is on the classpath).
When to Use Which
+-------------------------+---------------------------+| Use @ControllerAdvice | Use @RestControllerAdvice |+-------------------------+---------------------------+| MVC web apps | REST APIs || Returns view names | Returns JSON/XML || With Thymeleaf/JSP | With @ResponseBody logic |+-------------------------+---------------------------+Use @ControllerAdvice for MVC Views
If you’re building a traditional web application with server-side rendering (Thymeleaf, JSP, etc.), use @ControllerAdvice:
@ControllerAdvicepublic class MvcExceptionHandler {
@ExceptionHandler(AccessDeniedException.class) public String handleAccessDenied() { return "error/403"; // Returns view name, rendered by template engine }}Use @RestControllerAdvice for REST APIs
If you’re building a REST API that returns JSON, use @RestControllerAdvice:
@RestControllerAdvicepublic class ApiExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class) public ErrorResponse handleNotFound(ResourceNotFoundException ex) { return new ErrorResponse("NOT_FOUND", ex.getMessage()); }
@ExceptionHandler(ValidationException.class) public ErrorResponse handleValidation(ValidationException ex) { return new ErrorResponse("VALIDATION_ERROR", ex.getMessage()); }
// No @ResponseBody needed on any method}The Source Code Tells the Story
Looking at the Spring source code makes this crystal clear:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@ControllerAdvice@ResponseBody // <-- This is the only differencepublic @interface RestControllerAdvice { // ...}@RestControllerAdvice is literally @ControllerAdvice plus @ResponseBody.
Common Pitfalls
Pitfall 1: Using @ControllerAdvice Without @ResponseBody in REST API
@ControllerAdvicepublic class ApiExceptionHandler {
@ExceptionHandler(Exception.class) public ErrorResponse handle(Exception ex) { return new ErrorResponse("ERROR", ex.getMessage()); }}Result: Spring tries to resolve ErrorResponse as a view name. You’ll get a ServletException: Could not resolve view.
Pitfall 2: Using @RestControllerAdvice for MVC Views
@RestControllerAdvicepublic class MvcExceptionHandler {
@ExceptionHandler(Exception.class) public String handle(Exception ex) { return "error/500"; // This string becomes the response body, not a view name! }}Result: The string "error/500" is returned as plain text in the response body, not rendered as a Thymeleaf template.
Scoping Works the Same
Both annotations support the same scoping mechanisms:
// Only applies to controllers in specific packages@RestControllerAdvice(basePackages = "com.example.api")
// Only applies to controllers with specific annotation@RestControllerAdvice(annotations = RestController.class)
// Only applies to specific controller classes@RestControllerAdvice(assignableTypes = {UserController.class, OrderController.class})These scoping options work identically for both @ControllerAdvice and RestControllerAdvice.
Summary
| Feature | @ControllerAdvice | @RestControllerAdvice |
|---|---|---|
| Auto @ResponseBody | No | Yes |
| Best for | MVC web apps | REST APIs |
| Returns | View names or @ResponseBody values | Direct response body |
For REST APIs, just use @RestControllerAdvice. It’s the same as @ControllerAdvice but saves you from typing @ResponseBody on every method.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Spring Framework API: RestControllerAdvice
- 👨💻 Reddit Discussion: ControllerAdvice and RestControllerAdvice
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments