Learn the difference between @ControllerAdvice and @RestControllerAdvice in Spring Boot with clear explanations and end-to-end Java examples.
Introduction
Imagine you are building two applications using Spring Boot.
- One is a traditional web application that shows HTML pages in the browser.
- The other is a REST API used by a mobile app or frontend framework like React.
Now suppose something goes wrong—maybe a user is not found or an invalid request is sent.
Do you want to handle errors inside every controller? Of course not.
Spring provides a clean way to handle errors globally using @ControllerAdvice and @RestControllerAdvice.
Beginners often ask:
- Are these two annotations the same?
- When should I use which one?
- Why do REST APIs behave differently?
In this blog, we’ll explain the difference between @ControllerAdvice and @RestControllerAdvice using clear concepts and end-to-end working examples so you can confidently use them in real Spring Boot projects.
Core Concepts
What Is @ControllerAdvice?
@ControllerAdvice is used to handle exceptions globally across controllers.
Think of it as a central error handler for your application.
👉 Simple way to think about it
Instead of every controller handling errors on its own, a single manager handles them for everyone.
Key points:
- Works with
@Controller(MVC applications) - Typically returns HTML views or
ModelAndView - Requires
@ResponseBodyif you want JSON
Use cases:
- Web applications
- Server-side rendered pages (Thymeleaf, JSP)
- Custom error pages
What Is @RestControllerAdvice?
@RestControllerAdvice is designed specifically for REST APIs.
Technically, it is:
@ControllerAdvice + @ResponseBody
👉 Simple way to think about it
Instead of returning an error page, it returns structured JSON data.
Key points:
- Works best with
@RestController - Automatically converts responses to JSON
- Ideal for microservices and APIs
Use cases:
- REST APIs
- Mobile or frontend-based applications
- Microservices architecture
Key Difference at a Glance
| Feature | @ControllerAdvice | @RestControllerAdvice |
|---|---|---|
| Application type | MVC / Web apps | REST APIs |
| Default response | HTML / View | JSON |
| Needs @ResponseBody | Yes | No |
| Best suited for | UI-based apps | APIs & microservices |
End-to-End Code Examples (Java 21)
Below are complete working examples showing how each annotation is used in a real scenario.
Create spring starter project with spring-starter-web and thymeleaf dependencies
Example 1: End-to-End MVC Flow Using @ControllerAdvice
Step 1: Custom Exception
public class PageNotFoundException extends RuntimeException {
public PageNotFoundException(String message) {
super(message);
}
}
Step 2: MVC Controller
@Controller
@RequestMapping("/pages")
public class PageController {
@GetMapping("/{id}")
public String getPage(@PathVariable int id) {
if (id != 1) {
throw new PageNotFoundException("Page not found with id: " + id);
}
return "home"; // returns an HTML page
}
}
Step 3: Global Exception Handler Using @ControllerAdvice
@ControllerAdvice
public class GlobalMvcExceptionHandler {
@ExceptionHandler(PageNotFoundException.class)
public String handlePageNotFound(PageNotFoundException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "error"; // error.html
}
}
application.properties
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
server.error.whitelabel.enabled=false
error.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Error Page</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f8f9fa;
text-align: center;
padding-top: 80px;
}
.error-box {
background-color: #ffffff;
padding: 30px;
width: 450px;
margin: auto;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
color: #dc3545;
}
p {
font-size: 18px;
color: #555;
}
a {
text-decoration: none;
color: #007bff;
font-weight: bold;
}
</style>
</head>
<body>
<div class="error-box">
<h1>Oops! Something went wrong</h1>
<!-- Message sent from @ControllerAdvice -->
<p th:text="${errorMessage}">
An unexpected error occurred. Please try again later.
</p>
<br/>
<a href="/pages/1">Go Back to Home</a>
</div>
</body>
</html>
home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home Page</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f6f8;
text-align: center;
margin-top: 80px;
}
h1 {
color: #2c3e50;
}
p {
font-size: 18px;
color: #555;
}
</style>
</head>
<body>
<h1>Welcome to Spring MVC Home Page</h1>
<p>
This page is rendered using <strong>@Controller</strong>
and handled by <strong>@ControllerAdvice</strong>.
</p>
<p>
If an error occurs, you will be redirected to a custom error page.
</p>
</body>
</html>
ThymeleafConfig
@Configuration
@EnableWebMvc
public class ThymeleafConfig {
}
📌 End result:
- Browser receives an HTML error page
- Centralized error handling
- Clean controller code
Example 2: End-to-End REST API Flow Using @RestControllerAdvice
Step 1: Custom Exception
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
Step 2: Error Response Model
import java.time.LocalDateTime;
public record ErrorResponse(
int status,
String message,
LocalDateTime timestamp
) {}
Step 3: REST Controller
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public String getUser(@PathVariable Long id) {
if (!id.equals(1L)) {
throw new ResourceNotFoundException("User not found with id: " + id);
}
return "User found";
}
}
Step 4: Global Exception Handler Using @RestControllerAdvice
@RestControllerAdvice
public class GlobalRestExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(
ResourceNotFoundException ex) {
ErrorResponse response = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}
}
📌 End result (JSON response):
{
"status": 404,
"message": "User not found with id: 2",
"timestamp": "2025-01-10T12:30:45"
}
Best Practices
Use @RestControllerAdvice for REST APIs
This avoids missing@ResponseBody.Use @ControllerAdvice for MVC apps only
Especially when returning views or templates.Create consistent error response structures
Helps frontend teams and API consumers.Map correct HTTP status codes
Use 400, 404, 401, 500 appropriately.Avoid generic Exception handling first
Always handle specific exceptions beforeException.class.
Common Mistakes to Avoid
❌ Using @ControllerAdvice in REST APIs without @ResponseBody
❌ Returning HTML error pages from REST APIs
❌ Handling exceptions inside controllers
❌ Exposing stack traces in responses
Conclusion
The difference between @ControllerAdvice and @RestControllerAdvice lies in how your application communicates errors.
- Use @ControllerAdvice when building traditional web applications that return HTML.
- Use @RestControllerAdvice when building REST APIs that return JSON.
Both provide centralized, clean, and scalable exception handling. Once you understand this distinction, your Spring Boot applications will be easier to maintain and more professional—both in real projects and interviews.
Top comments (0)