Global Exception Handling in REST APIs (ResponseEntityExceptionHandler)
Slug: global-exception-handling-rest-apis-responseentityexceptionhandler
Description: Learn how to use Spring’s ResponseEntityExceptionHandler for global exception handling in REST APIs. Best practices, custom error responses, and performance tips.
Tags: Java exception handling, REST API exceptions, Spring Boot error handling, ResponseEntityExceptionHandler, @ControllerAdvice, @ExceptionHandler, checked vs unchecked exceptions, custom exceptions, logging best practices, exception translation
Category: Java
Series: Java-Exception-Handling
Introduction
In modern REST APIs, exception handling is as important as business logic. A well-designed API must handle failures gracefully, provide meaningful error responses, and maintain consistent behavior across endpoints. In Spring MVC and Spring Boot, this is often achieved using the powerful ResponseEntityExceptionHandler in combination with @ControllerAdvice
and @ExceptionHandler
.
Think of it like airbags in a car: you hope never to need them, but when things go wrong, they save the day by ensuring safety and consistency.
Core Definition and Purpose of Java Exception Handling
Java exception handling provides a structured mechanism to deal with runtime anomalies. In REST APIs, exception handling ensures:
- Consistent error format for clients
- Separation of concerns between business logic and error handling
- Improved debugging and monitoring with proper logs
- Resilient applications that degrade gracefully instead of crashing
Errors vs Exceptions in Java
- Error: Irrecoverable conditions (e.g.,
OutOfMemoryError
). You should not catch or handle these. - Exception: Conditions that can be anticipated and recovered from. These are the focus of REST exception handling.
- Throwable: The root class for both
Error
andException
.
Exception Hierarchy in Context of REST APIs
Throwable
├── Error
└── Exception
├── IOException
├── SQLException
└── RuntimeException
├── NullPointerException
├── IllegalArgumentException
└── CustomDomainException
In REST APIs, most exceptions are subclasses of RuntimeException
, often wrapped into custom exceptions.
Checked vs Unchecked Exceptions
- Checked exceptions: Must be declared with
throws
. Examples:IOException
,SQLException
. - Unchecked exceptions: Subclasses of
RuntimeException
. Examples:NullPointerException
,IllegalStateException
.
In REST APIs, unchecked exceptions are preferred because they simplify signatures and allow global handling via ResponseEntityExceptionHandler
.
Basic Syntax: try, catch, finally
Even in REST APIs, local handling may be necessary:
try {
service.processData();
} catch (IOException ex) {
throw new CustomApiException("I/O error while processing", ex);
}
But global strategies are preferred to avoid duplication.
Global Exception Handling with @ControllerAdvice
Spring’s @ControllerAdvice
allows you to define centralized exception handling across controllers.
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiError> handleResourceNotFound(ResourceNotFoundException ex) {
ApiError error = new ApiError(HttpStatus.NOT_FOUND, ex.getMessage());
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiError> handleGeneralException(Exception ex) {
ApiError error = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected error");
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Using ResponseEntityExceptionHandler
ResponseEntityExceptionHandler
is a Spring class that provides default implementations for common Spring MVC exceptions (like HttpMessageNotReadableException
). You can extend it and override methods for customization:
@ControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(err -> err.getField() + ": " + err.getDefaultMessage())
.toList();
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, "Validation Failed", errors);
return new ResponseEntity<>(apiError, HttpStatus.BAD_REQUEST);
}
}
Exception Chaining and Root Cause Tracking
REST APIs should preserve root cause information. Always chain exceptions:
throw new CustomApiException("Database operation failed", ex);
This ensures stack traces and logs retain the original failure cause.
Real-World Scenarios
File I/O
If a file upload fails due to permissions, throw a custom exception and map it to HttpStatus.FORBIDDEN
.
Database Access (JDBC)
Convert SQLException
into a domain-specific exception like DataAccessException
.
REST APIs
Invalid input should result in 400 BAD REQUEST
with a clear error payload.
Multithreading and Async
Exceptions in CompletableFuture
tasks can be captured using .exceptionally()
and wrapped into API responses.
Best Practices
- Always return structured error responses (JSON/XML).
- Log exceptions at appropriate levels (
ERROR
for critical,WARN
for recoverable). - Don’t leak internal details (e.g., stack traces) in responses.
- Favor unchecked exceptions for REST APIs.
- Centralize exception handling with
@ControllerAdvice
.
Common Anti-Patterns
- Swallowing exceptions without logging.
- Returning different error formats across endpoints.
- Over-catching (catching
Exception
unnecessarily). - Mixing business logic and exception handling inside controllers.
Performance Considerations
- Declaring try-catch has negligible cost.
- Throwing exceptions is expensive, avoid using them for control flow.
- Use validation frameworks (like Hibernate Validator) to reduce exceptions.
📌 What's New in Java Versions?
- Java 7+: Multi-catch, try-with-resources for I/O.
- Java 8: Lambdas & Streams — handling exceptions in functional interfaces.
- Java 9+: Stack-Walking API for efficient exception analysis.
- Java 14+: Helpful
NullPointerException
messages for debugging. - Java 21: Structured concurrency improves exception handling in async tasks.
FAQ
Q1. Why not catch Error
?
Because Error
represents JVM-level issues (like OutOfMemoryError
), which are not recoverable.
Q2. What’s the performance cost of try-catch in APIs?
Minimal for declared blocks. Expensive only when exceptions are frequently thrown.
Q3. How do I translate low-level exceptions?
Wrap them in custom domain exceptions and handle globally.
Q4. Can I log stack traces in production?
Yes, but at appropriate log levels and without exposing them to API consumers.
Q5. What if multiple exceptions map to the same status code?
Unify them into a single handler returning a consistent error payload.
Q6. How does Spring Boot simplify this?
It auto-configures ErrorController
and provides /error
endpoints.
Q7. Should I use checked or unchecked exceptions in REST APIs?
Prefer unchecked for simplicity, and handle them globally.
Q8. Can I send custom headers with error responses?
Yes, ResponseEntity
allows full customization of headers and body.
Q9. How do I handle async exceptions with CompletableFuture
?
Use .exceptionally()
or handle()
to capture and wrap them.
Q10. How to avoid leaking sensitive info in error messages?
Always sanitize error responses, exposing only user-relevant info.
Conclusion and Key Takeaways
Global exception handling in REST APIs ensures consistency, reliability, and maintainability. Spring’s ResponseEntityExceptionHandler
simplifies this by offering a centralized way to intercept, log, and respond to failures. Always design error handling as carefully as business logic — it’s the contract your API exposes to the world.