Global Exception Handling in Spring Boot REST APIs (Deep Guide)
Exception handling is one of the most critical parts of building reliable Spring Boot REST APIs. Poor error handling leads to inconsistent responses, security leaks, and debugging nightmares. Spring Boot provides a powerful way to centralize all exceptions using @ControllerAdvice and @ExceptionHandler.
This guide goes far beyond basics, covering:
- How exception handling works internally in Spring
- Custom exception structures + best practices
- Real-world JSON error models
- Global handler vs local handler
- Validation errors, SQL errors, 404 errors
- Advanced production-grade exception architecture
- 12+ interview-ready questions and answers
๐ฅ Why Global Exception Handling Matters
Without centralized handling, you will end up:
- Returning different JSON structures from different controllers
- Exposing internal stack traces to clients
- Managing many
try-catchblocks cluttering code - Wasting time debugging inconsistent logs
๐งฉ Understanding the Core Annotations
@ControllerAdvice
A Spring annotation used to define global exception handling logic. It applies across all controllers automatically.
@ExceptionHandler
Handles a specific exception type and returns a custom response.
⚙️ How Spring Processes Exceptions (Internal Flow)
Controller → Exception Thrown → DispatcherServlet
→ HandlerExceptionResolver
→ @ControllerAdvice / @ExceptionHandler
→ JSON Error Response (HttpMessageConverter)
๐ Step 1 — Create a Custom Exception
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
๐ Step 2 — Standard Error Response Model (Best Practice)
public record ApiError(
LocalDateTime timestamp,
int status,
String error,
String message,
String path
) {}
This creates clean, structured, JSON-friendly error models.
๐ Step 3 — Create Global Exception Handler
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity handleNotFound(
ResourceNotFoundException ex,
HttpServletRequest request) {
ApiError error = new ApiError(
LocalDateTime.now(),
HttpStatus.NOT_FOUND.value(),
"Not Found",
ex.getMessage(),
request.getRequestURI()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity handleGeneral(
Exception ex,
HttpServletRequest request) {
ApiError error = new ApiError(
LocalDateTime.now(),
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"Internal Error",
ex.getMessage(),
request.getRequestURI()
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
path and timestamp in error responses.
It makes debugging significantly easier.
๐ Step 4 — Throw Exception From Controller
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
@GetMapping("/{id}")
public Employee getEmployee(@PathVariable Long id) {
return employeeRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException(
"Employee not found with id " + id));
}
}
๐งช Example JSON Error Response
{
"timestamp": "2025-05-18T12:20:10",
"status": 404,
"error": "Not Found",
"message": "Employee not found with id 15",
"path": "/api/employees/15"
}
๐ Handling Validation Errors
Spring automatically throws MethodArgumentNotValidException for invalid DTOs.
Global Handler for Validation Errors:
@ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntityhandleValidation(MethodArgumentNotValidException ex, HttpServletRequest req) { String msg = ex.getBindingResult() .getAllErrors() .get(0) .getDefaultMessage(); ApiError error = new ApiError( LocalDateTime.now(), 400, "Validation Failed", msg, req.getRequestURI() ); return ResponseEntity.badRequest().body(error); }
๐จ Common Mistakes to Avoid
- ❌ Returning different error formats for different exceptions
- ❌ Catching exceptions inside controller unnecessarily
- ❌ Not logging errors in global handlers
- ❌ Sending stack trace to clients (security risk!)
- ❌ Not handling validation errors properly
๐️ Production-Grade Best Practices
- Use record-based error models (
ApiError) - Never expose internal exception messages directly
- Add correlation/request IDs in logs for debugging
- Log full stack trace on the server, return a clean message to user
- Separate client errors (4xx) from server errors (5xx)
๐ง FAQ (Most Asked Interview Questions)
1. What is the difference between @ExceptionHandler and @ControllerAdvice?
@ExceptionHandler handles exceptions inside a single controller. @ControllerAdvice handles exceptions globally across all controllers.
2. What is the default error response in Spring Boot?
Spring Boot returns a JSON response using BasicErrorController.
3. Should we use try-catch in controllers?
No. Exceptions should bubble up to the global handler.
4. Can we have multiple global exception handlers?
Yes, but Spring will choose the most specific one first.
5. How do we log exceptions properly?
Use Logger inside the global handler and avoid logging sensitive info.
✅ Conclusion
Global exception handling is a mandatory practice for any production-ready Spring Boot REST API. Using @ControllerAdvice + @ExceptionHandler lets you build consistent, maintainable, secure, and clean error responses across your entire application.