Module: Exception Handling

@ControllerAdvice

This section dives into @ControllerAdvice, a powerful annotation in Spring Boot for centralized exception handling. We'll build upon the basic exception handling concepts and learn how to create a global approach to managing errors in your application.

What is @ControllerAdvice?

@ControllerAdvice is a component annotation. It combines @Component and @ControllerAdvice. Essentially, it allows you to define exception handling logic that applies to all controllers in your application, without having to repeat the same code in each controller. Think of it as a global exception handler.

Key Benefits:

  • Centralized Error Handling: Avoid code duplication by handling exceptions in a single place.
  • Consistent Error Responses: Ensure a uniform error response format across your entire application.
  • Improved Maintainability: Easier to update and maintain error handling logic.
  • Global Configuration: Apply common error handling configurations (like logging or custom error pages) globally.

Creating a @ControllerAdvice Class

Let's create a simple @ControllerAdvice class.

package com.example.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

@org.springframework.web.bind.annotation.ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ResponseBody
    public ErrorResponse handleResourceNotFoundException(ResourceNotFoundException ex) {
        return new ErrorResponse(ex.getMessage());
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorResponse handleIllegalArgumentException(IllegalArgumentException ex) {
        return new ErrorResponse(ex.getMessage());
    }

    @ExceptionHandler(Exception.class) // Catch-all for other exceptions
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ErrorResponse handleGenericException(Exception ex) {
        return new ErrorResponse("An unexpected error occurred: " + ex.getMessage());
    }
}

Explanation:

  • @ControllerAdvice: Marks this class as a global exception handler.
  • @ExceptionHandler(ExceptionType.class): This annotation specifies which exception type this method will handle. You can have multiple @ExceptionHandler methods to handle different exception types.
  • @ResponseStatus(HttpStatus.status): Sets the HTTP status code to be returned with the error response. This is crucial for RESTful APIs.
  • @ResponseBody: Indicates that the return value of the method should be written directly to the response body (e.g., as JSON).
  • ErrorResponse: A custom class to encapsulate the error message. (See example below).
  • Order of Exception Handlers: Spring processes @ExceptionHandler methods in the order they are defined. More specific handlers should come before more general handlers (like the Exception.class handler).

Creating a Custom Error Response Class

Let's define the ErrorResponse class:

package com.example.demo.exception;

public class ErrorResponse {
    private String message;

    public ErrorResponse(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

This simple class holds the error message that will be returned in the response. You can extend this class to include more information, such as error codes, timestamps, or debugging details.

Example Usage

Let's assume you have a controller like this:

package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

    @GetMapping("/resource/{id}")
    public String getResource(@PathVariable Long id) {
        if (id < 0) {
            throw new IllegalArgumentException("ID must be positive");
        }
        if (id > 100) {
            throw new ResourceNotFoundException("Resource with ID " + id + " not found");
        }
        return "Resource with ID: " + id;
    }
}

And the custom exception:

package com.example.demo.exception;

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

Now, if you make a request to /resource/-1, you'll get a 400 Bad Request with a JSON response like:

{
  "message": "ID must be positive"
}

If you request /resource/101, you'll get a 404 Not Found with a JSON response like:

{
  "message": "Resource with ID 101 not found"
}

If any other unhandled exception occurs, you'll get a 500 Internal Server Error.

Advanced @ControllerAdvice Features

  • @InitBinder: Used to customize data binding for specific types of requests.
  • @ModelAttribute: Used to add attributes to the model before a controller method is invoked.
  • @BeforeControllerMethod (Spring 6+): Allows you to execute code before a controller method is called. Similar to AOP's @Before advice.
  • Customizing Error Pages: You can return a view name instead of a @ResponseBody to render a custom error page. Configure your view resolver accordingly.
  • Logging: Within your @ExceptionHandler methods, you can log the exception details for debugging purposes.

Best Practices

  • Specificity: Handle specific exceptions whenever possible. Avoid overly broad Exception handlers unless absolutely necessary.
  • Meaningful Error Messages: Provide clear and informative error messages to help clients understand the problem.
  • Consistent Error Format: Use a consistent error response format (e.g., JSON with a message field) across your application.
  • Logging: Log exceptions for debugging and monitoring.
  • Testing: Write unit tests to verify that your exception handling logic works correctly.

@ControllerAdvice is a cornerstone of robust exception handling in Spring Boot. By centralizing your error handling logic, you can create more maintainable, consistent, and user-friendly applications.