🔥 Launch Offer: Interview Preparation Bundle @ ₹2,499 ₹9,999 • 75% OFF. Offer expires in 🔥 Launch Offer: Interview Preparation Bundle @ ₹2,499 ₹9,999 • 75% OFF. Offer expires in Interview Preparation Bundle @ ₹2,499 ₹9,999

00:00:00 Get Bundle
home spring boot spring-boot-global-exception-handling-explained-controlleradvice-restcontrolleradvice-exceptionhandler-with-examples
Interview guide Spring Boot 26 sections

Spring Boot Global Exception Handling Explained: @ControllerAdvice, @RestControllerAdvice & @ExceptionHandler with Examples

Exception handling is one of the most important aspects of building a robust REST API in Spring Boot. Every application encounters unexpected situations, such as invalid input, missing…

Start reading
By javainterviewprep June 15, 2026 7 min read

Exception handling is one of the most important aspects of building a robust REST API in Spring Boot. Every application encounters unexpected situations, such as invalid input, missing resources, database failures, or internal server errors. If these exceptions are not handled properly, Spring Boot returns default error responses that expose technical details and provide a poor experience for API consumers.

A well-designed exception handling mechanism makes your APIs more secure, easier to maintain, and more user-friendly. Instead of exposing stack traces, you can return meaningful error messages with appropriate HTTP status codes in a consistent format.

Spring Boot provides three powerful annotations for centralized exception handling:

  • @ControllerAdvice
  • @RestControllerAdvice
  • @ExceptionHandler

In this tutorial, you’ll learn each annotation in detail with practical examples, execution flow, and best practices used in real-world Spring Boot applications.


Why Do We Need Global Exception Handling?

Consider a simple REST API that retrieves user information.

@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
private UserService service;

@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return service.getUser(id);
}
}

Now suppose the requested user does not exist.

The service throws an exception.

throw new UserNotFoundException("User not found");

Without any exception handling, Spring Boot returns its default error response.

{
"timestamp": "2026-06-15T12:30:00",
"status": 500,
"error": "Internal Server Error",
"trace": "Very long stack trace..."
}

This response has several problems:

  • Exposes internal implementation details
  • Difficult for frontend developers to understand
  • Inconsistent API responses
  • Poor user experience

Instead, we should return a clean response like this:

{
"status": 404,
"message": "User not found",
"timestamp": "2026-06-15T12:30:00"
}

This is where Spring Boot’s global exception handling becomes useful.


What is @ControllerAdvice?

@ControllerAdvice is a Spring annotation that allows you to define global exception handling logic for all controllers in your application.

Instead of writing try-catch blocks inside every controller method, you write exception handling code once in a single class.

Think of it as a centralized error management system.

Without @ControllerAdvice

UserController
├── try-catch
ProductController
├── try-catch
OrderController
├── try-catch
EmployeeController
├── try-catch

With @ControllerAdvice

UserController
ProductController
OrderController
EmployeeController

│
â–¼

GlobalExceptionHandler

All controllers share the same exception handler.


Problem Without @ControllerAdvice

Imagine the following controller.

@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
private UserService service;

@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {

try {

return service.getUser(id);

} catch (UserNotFoundException ex) {

throw ex;
}
}
}

Now every controller starts looking similar.

try {

}
catch(Exception ex){

}

Repeated everywhere.

This creates duplicate code and makes maintenance difficult.


Using @ControllerAdvice

Now let’s remove all exception handling from the controller.

@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
private UserService service;

@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {

return service.getUser(id);

}

}

The controller now contains only business logic.

Exception handling is moved to one separate class.

@ControllerAdvice
public class GlobalExceptionHandler {

}

This class can now handle exceptions thrown from every controller.


What is @ExceptionHandler?

@ExceptionHandler tells Spring which method should execute whenever a specific exception occurs.

General syntax

@ExceptionHandler(ExceptionClass.class)
public ResponseEntity<?> methodName(ExceptionClass ex){

}

Example

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex){

return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ex.getMessage());

}

}

Whenever UserNotFoundException is thrown, Spring automatically calls this method.

No manual invocation is required.


Complete Working Example

Step 1: Create User Entity

public class User {

private Long id;
private String name;

public User(Long id, String name) {
this.id = id;
this.name = name;
}

// getters and setters
}

Step 2: Repository

@Repository
public class UserRepository {

public User findById(Long id){

return null;

}

}

For demonstration purposes, the repository always returns null.


Step 3: Create Custom Exception

public class UserNotFoundException extends RuntimeException {

public UserNotFoundException(String message){

super(message);

}

}

Step 4: Service Layer

@Service
public class UserService {

@Autowired
private UserRepository repository;

public User getUser(Long id){

User user = repository.findById(id);

if(user == null){

throw new UserNotFoundException(
"User with ID " + id + " not found");

}

return user;

}

}

Notice that the service throws the exception.

It does not handle it.


Step 5: Controller

@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
private UserService service;

@GetMapping("/{id}")
public User getUser(@PathVariable Long id){

return service.getUser(id);

}

}

Still no try-catch.


Step 6: Global Exception Handler

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex){

return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ex.getMessage());

}

}

Now every time UserNotFoundException occurs, Spring automatically executes this method.


Complete Request Flow

Client
│
â–¼
UserController
│
â–¼
UserService
│
â–¼
UserRepository
│
â–¼
Database
│
â–¼
User not found
│
â–¼
throw UserNotFoundException
│
â–¼
@ControllerAdvice
│
â–¼
@ExceptionHandler
│
â–¼
ResponseEntity
│
â–¼
Client

Returning Custom JSON Response

Returning only a string is not considered a good REST API practice.

Instead, create an error response object.

public class ErrorResponse {

private LocalDateTime timestamp;
private int status;
private String message;

public ErrorResponse(LocalDateTime timestamp,
int status,
String message) {

this.timestamp = timestamp;
this.status = status;
this.message = message;
}

// getters
}

Update the exception handler.

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex){

ErrorResponse response = new ErrorResponse(

LocalDateTime.now(),
HttpStatus.NOT_FOUND.value(),
ex.getMessage()

);

return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(response);

}

}

API Response

{
"timestamp": "2026-06-15T12:45:10",
"status": 404,
"message": "User with ID 10 not found"
}

This approach is much cleaner and easier for frontend applications to consume.


Handling Multiple Exceptions

One global exception handler can manage many exceptions.

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleUser(UserNotFoundException ex){

return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ex.getMessage());

}

@ExceptionHandler(ProductNotFoundException.class)
public ResponseEntity<String> handleProduct(ProductNotFoundException ex){

return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ex.getMessage());

}

@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<String> handleOrder(OrderNotFoundException ex){

return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ex.getMessage());

}

}

Spring automatically selects the appropriate handler based on the exception type.


Handling All Exceptions

Sometimes unexpected exceptions occur.

Instead of exposing internal server details, handle them globally.

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception ex){

return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Something went wrong.");

}

}

This serves as a fallback handler for all unhandled exceptions.


What is @RestControllerAdvice?

@RestControllerAdvice is simply a shortcut annotation.

Internally, it combines:

@ControllerAdvice
@ResponseBody

Instead of writing

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

}

You can simply write

@RestControllerAdvice
public class GlobalExceptionHandler {

}

For REST APIs, this is the recommended approach.


Difference Between @ControllerAdvice and @RestControllerAdvice

Feature@ControllerAdvice@RestControllerAdvice
Used withSpring MVC ControllersREST APIs
Returns ViewsYesNo
Automatically adds @ResponseBodyNoYes
Suitable for JSON APIsRequires @ResponseBodyYes
Recommended for REST APIsNoYes

Best Practices

  • Keep controllers free from try-catch blocks.
  • Throw meaningful custom exceptions from the service layer.
  • Handle exceptions centrally using @RestControllerAdvice.
  • Return structured JSON responses instead of plain text.
  • Use appropriate HTTP status codes such as 404, 400, 401, 403, and 500.
  • Avoid exposing stack traces or sensitive internal details in API responses.
  • Create a common ErrorResponse class for consistent responses across the application.

Frequently Asked Interview Questions

1. What is @ControllerAdvice?

It is a Spring annotation used to define global exception handling, global model attributes, and data binding logic that applies across multiple controllers.


2. What is @RestControllerAdvice?

It is a specialized version of @ControllerAdvice that automatically adds @ResponseBody, making it ideal for REST APIs returning JSON responses.


3. What is @ExceptionHandler?

@ExceptionHandler maps a specific exception type to a method. Whenever that exception is thrown during request processing, Spring invokes the corresponding handler method automatically.


4. Why should we use global exception handling?

Global exception handling:

  • Eliminates duplicate try-catch blocks
  • Keeps controllers clean
  • Provides consistent API responses
  • Improves maintainability
  • Makes APIs easier for frontend applications to consume

5. Can one @ControllerAdvice handle multiple exceptions?

Yes. A single global exception handler class can contain multiple @ExceptionHandler methods, each handling a different exception type.


Conclusion

Global exception handling is a fundamental part of every Spring Boot REST API. By using @ControllerAdvice, @RestControllerAdvice, and @ExceptionHandler, you can centralize your exception handling logic, keep controllers clean, and return consistent, user-friendly error responses.

In real-world applications, this approach improves code maintainability, enhances API security by preventing stack trace exposure, and provides a better developer experience for frontend teams consuming your APIs.

If you’re preparing for Spring Boot or Java backend interviews, mastering these annotations is essential, as they are among the most frequently asked topics in technical interviews.