Skip to content

@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:

GlobalExceptionHandler.java
@ControllerAdvice
public 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

GlobalExceptionHandler.java
@ControllerAdvice
public 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

GlobalExceptionHandler.java
@RestControllerAdvice // <-- This includes @ResponseBody automatically
public 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:

MvcExceptionHandler.java
@ControllerAdvice
public 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:

ApiExceptionHandler.java
@RestControllerAdvice
public 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:

RestControllerAdvice.java (Spring Framework)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody // <-- This is the only difference
public @interface RestControllerAdvice {
// ...
}

@RestControllerAdvice is literally @ControllerAdvice plus @ResponseBody.

Common Pitfalls

Pitfall 1: Using @ControllerAdvice Without @ResponseBody in REST API

Wrong: Missing @ResponseBody
@ControllerAdvice
public 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

Wrong: @ResponseBody prevents view resolution
@RestControllerAdvice
public 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:

Scoped Exception Handlers
// 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 @ResponseBodyNoYes
Best forMVC web appsREST APIs
ReturnsView names or @ResponseBody valuesDirect 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:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments