Global Exception Handling in REST APIs (ResponseEntityExceptionHandler)

Illustration for Global Exception Handling in REST APIs (ResponseEntityExceptionHandler)
By Last updated:

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 and Exception.

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.