Global Exception Handling with @ControllerAdvice


Table of Contents

  1. Introduction
  2. Overview of Exception Handling in Spring
  3. Using @ControllerAdvice for Global Exception Handling
  4. Implementing Global Exception Handler
  5. Handling Specific Exceptions
  6. Customizing Response for Exceptions
  7. Conclusion

1. Introduction

In real-world applications, error handling is a crucial part of maintaining system stability and providing meaningful feedback to users. In a Spring Boot application, exception handling allows you to catch and respond to different types of errors that may occur during request processing.

While handling exceptions locally in controllers using @ExceptionHandler is useful, a more efficient approach is to centralize exception handling for the entire application using @ControllerAdvice. This approach ensures that common exception handling logic is reused across multiple controllers, improving maintainability and readability.

In this module, we will explore how to use @ControllerAdvice for global exception handling in Spring Boot applications.


2. Overview of Exception Handling in Spring

Spring provides robust exception handling mechanisms, which include:

  • @ExceptionHandler: Used at the method level within controllers to handle specific exceptions.
  • @ControllerAdvice: A specialized class for global exception handling, allowing you to define centralized exception handling logic for all controllers.
  • ResponseEntityExceptionHandler: A base class that provides default exception handling, which you can override to customize behavior.

Key Advantages of Using @ControllerAdvice:

  • Centralized exception handling across the entire application.
  • Reduced code duplication.
  • The ability to handle different types of exceptions globally and return consistent error responses.

3. Using @ControllerAdvice for Global Exception Handling

@ControllerAdvice is a Spring annotation that provides global exception handling across all controllers in your application. By applying @ControllerAdvice to a class, you can define methods that handle various types of exceptions thrown during the processing of HTTP requests.

Basic Structure of @ControllerAdvice:

javaCopyEdit@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> handleException(Exception ex) {
        // Handle the exception and return a custom response
        return new ResponseEntity<>("An error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

In this example:

  • The @ControllerAdvice class defines a global exception handler for all controllers.
  • The @ExceptionHandler annotation specifies that the handleException method should be invoked for any Exception thrown during request processing.

4. Implementing Global Exception Handler

A typical global exception handler in a Spring Boot application might handle a variety of exceptions, such as validation errors, resource not found errors, or any unexpected runtime exceptions. Below is an example of a GlobalExceptionHandler class that handles various exceptions.

Example of GlobalExceptionHandler:

javaCopyEdit@ControllerAdvice
public class GlobalExceptionHandler {

    // Handle resource not found exceptions
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFoundException ex) {
        ErrorResponse errorResponse = new ErrorResponse("RESOURCE_NOT_FOUND", ex.getMessage());
        return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
    }

    // Handle validation errors (MethodArgumentNotValidException)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex) {
        List<String> errorMessages = ex.getBindingResult().getAllErrors().stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.toList());
        
        ErrorResponse errorResponse = new ErrorResponse("VALIDATION_FAILED", errorMessages);
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }

    // Handle generic runtime exceptions
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<Object> handleRuntimeException(RuntimeException ex) {
        ErrorResponse errorResponse = new ErrorResponse("INTERNAL_SERVER_ERROR", ex.getMessage());
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    // Handle all other exceptions
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> handleGenericException(Exception ex) {
        ErrorResponse errorResponse = new ErrorResponse("GENERAL_ERROR", ex.getMessage());
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

In this example:

  • handleResourceNotFoundException: Handles the ResourceNotFoundException and returns a 404 Not Found status.
  • handleValidationExceptions: Handles validation errors that occur when input does not meet the expected criteria.
  • handleRuntimeException: Catches any runtime exceptions and returns an appropriate response.
  • handleGenericException: Handles all other exceptions, providing a generic response with a 500 Internal Server Error.

5. Handling Specific Exceptions

Using @ControllerAdvice, you can handle specific types of exceptions and return custom responses for each type. For example:

Handling ResourceNotFoundException:

javaCopyEditpublic class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

In the @ControllerAdvice class, you can catch and handle this exception:

javaCopyEdit@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFoundException ex) {
    ErrorResponse errorResponse = new ErrorResponse("RESOURCE_NOT_FOUND", ex.getMessage());
    return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
}

Handling MethodArgumentNotValidException:

When you apply @Valid or @Validated to a method parameter in your controllers, validation errors are thrown as MethodArgumentNotValidException. You can handle this exception to return specific validation error messages:

javaCopyEdit@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex) {
    List<String> errorMessages = ex.getBindingResult().getAllErrors().stream()
            .map(DefaultMessageSourceResolvable::getDefaultMessage)
            .collect(Collectors.toList());
    
    ErrorResponse errorResponse = new ErrorResponse("VALIDATION_FAILED", errorMessages);
    return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}

6. Customizing Response for Exceptions

To maintain consistency in your application’s error responses, you may want to define a custom error response format. This can be done by creating an ErrorResponse class, which will be used to structure all the error messages returned by your exception handler.

Example of ErrorResponse Class:

javaCopyEditpublic class ErrorResponse {
    private String errorCode;
    private Object errorDetails;

    public ErrorResponse(String errorCode, Object errorDetails) {
        this.errorCode = errorCode;
        this.errorDetails = errorDetails;
    }

    // Getters and Setters
}

With this ErrorResponse class, you can return consistent error messages across all controllers, ensuring that clients receive predictable and structured error responses.


7. Conclusion

In this module, we have covered the following topics related to global exception handling in Spring Boot applications:

  • @ControllerAdvice: A specialized annotation for global exception handling.
  • Exception Handling: How to handle different types of exceptions, such as validation errors, resource not found errors, and runtime exceptions.
  • Custom Error Responses: How to structure your error responses for consistency.
  • @ExceptionHandler: How to create methods to handle specific exceptions and return meaningful error messages.

Using @ControllerAdvice for centralized exception handling enhances your application’s maintainability, readability, and provides consistent error responses for clients, making it an essential part of building robust Spring Boot applications.