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@ExceptionHandlermethods 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
@ExceptionHandlermethods in the order they are defined. More specific handlers should come before more general handlers (like theException.classhandler).
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@Beforeadvice.- Customizing Error Pages: You can return a view name instead of a
@ResponseBodyto render a custom error page. Configure your view resolver accordingly. - Logging: Within your
@ExceptionHandlermethods, you can log the exception details for debugging purposes.
Best Practices
- Specificity: Handle specific exceptions whenever possible. Avoid overly broad
Exceptionhandlers 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
messagefield) 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.