Spring Boot Exception Handling Tutorial with @RestControllerAdvice
Introduction
Exception handling is a critical part of every Spring Boot application. When errors occur, applications should return meaningful and user-friendly responses instead of exposing internal implementation details.
Without proper exception handling, users may receive confusing error messages or stack traces that make debugging difficult and can expose sensitive information.
In this tutorial, you'll learn how to implement exception handling in Spring Boot using custom exceptions, @ExceptionHandler, and @RestControllerAdvice.
What is Exception Handling?
Exception handling is the process of catching and managing runtime errors that occur during application execution.
Examples:
- Employee not found
- Invalid request data
- Database connection failure
- Duplicate records
- Unauthorized access
Instead of crashing the application, Spring Boot can return meaningful responses.
Why Exception Handling is Important
Benefits:
- Better API responses
- Improved user experience
- Cleaner code
- Easier debugging
- Enhanced security
Default Spring Boot Error Response
Without custom exception handling:
{ "timestamp":"2026-07-25T10:15:00", "status":500, "error":"Internal Server Error", "path":"/employees/100" }
This response does not clearly explain the actual problem.
Create a Custom Exception
EmployeeNotFoundException.java
public class EmployeeNotFoundException extends RuntimeException { public EmployeeNotFoundException(Long id) { super("Employee not found with id: " + id); } }
Throw Exception from Service Layer
EmployeeService.java
public Employee getEmployee(Long id) { return repository.findById(id) .orElseThrow(() -> new EmployeeNotFoundException(id)); }
When an employee is not found, a custom exception is thrown.
Handle Exception in Controller
@ExceptionHandler( EmployeeNotFoundException.class) public ResponseEntity<String> handleEmployeeNotFound( EmployeeNotFoundException ex) { return ResponseEntity .status(HttpStatus.NOT_FOUND) .body(ex.getMessage()); }
What is @ExceptionHandler?
@ExceptionHandler is used to catch specific exceptions and return custom responses.
Benefits:
- Cleaner code
- Better error messages
- Custom HTTP status codes
Global Exception Handling
As applications grow, handling exceptions inside every controller becomes difficult.
Spring Boot provides:
@RestControllerAdvice
for centralized exception handling.
Create GlobalExceptionHandler
GlobalExceptionHandler.java
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler( EmployeeNotFoundException.class) public ResponseEntity<String> handleEmployeeNotFound( EmployeeNotFoundException ex) { return ResponseEntity .status(HttpStatus.NOT_FOUND) .body(ex.getMessage()); } }
Now all controllers can use the same exception handler.
Create Structured Error Response
Instead of returning plain text:
Employee not found
Create a reusable error object.
ErrorResponse.java
public class ErrorResponse { private String message; private int status; private LocalDateTime timestamp; public ErrorResponse( String message, int status, LocalDateTime timestamp) { this.message = message; this.status = status; this.timestamp = timestamp; } }
Return Custom Error JSON
@ExceptionHandler( EmployeeNotFoundException.class) public ResponseEntity<ErrorResponse> handleEmployeeNotFound( EmployeeNotFoundException ex) { ErrorResponse error = new ErrorResponse( ex.getMessage(), 404, LocalDateTime.now()); return ResponseEntity .status(HttpStatus.NOT_FOUND) .body(error); }
Error Response Example
{ "message":"Employee not found with id: 100", "status":404, "timestamp":"2026-07-25T10:20:00" }
Handling Validation Exceptions
If you completed our Validation Tutorial, you can also handle:
MethodArgumentNotValidException
Example
@ExceptionHandler( MethodArgumentNotValidException.class) public ResponseEntity<Map<String,String>> handleValidationExceptions( MethodArgumentNotValidException ex) { Map<String,String> errors = new HashMap<>(); ex.getBindingResult() .getFieldErrors() .forEach(error -> errors.put( error.getField(), error.getDefaultMessage())); return ResponseEntity .badRequest() .body(errors); }
Common HTTP Status Codes
| Status Code | Meaning |
|---|---|
| 200 | OK |
| 201 | Created |
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not Found |
| 500 | Internal Server Error |
Exception Handling Flow
Client Request ↓ Controller ↓ Service Layer ↓ Exception Thrown ↓ @RestControllerAdvice ↓ Custom Error Response ↓ Client
Best Practices
Create Custom Exceptions
Avoid generic RuntimeExceptions.
Use Global Exception Handling
Prefer:
@RestControllerAdvice
Return Consistent Responses
Use a common error response structure.
Avoid Exposing Stack Traces
Never expose internal implementation details to clients.
Log Exceptions
Use SLF4J logging for troubleshooting.
Common Mistakes
Catching Generic Exception
Bad:
catch(Exception ex)
Prefer specific exceptions.
Returning Internal Error Details
Never expose:
- SQL queries
- Stack traces
- Server configuration
Duplicating Exception Logic
Use centralized exception handling.
Interview Questions
What is @ExceptionHandler?
It catches and processes specific exceptions.
What is @RestControllerAdvice?
It provides global exception handling for REST APIs.
Why create custom exceptions?
Custom exceptions make code cleaner and provide meaningful error messages.
What is ResponseEntity?
ResponseEntity allows complete control over HTTP responses.
FAQ
What is the difference between @ControllerAdvice and @RestControllerAdvice?
@RestControllerAdvice automatically returns JSON responses.
Can multiple exceptions be handled?
Yes.
Can I define custom error objects?
Yes.
Is exception handling required in REST APIs?
Strongly recommended for production applications.
