Global Exception Handling in Spring Boot
So far, we've seen how to handle exceptions within specific controller methods. But what if we want a centralized way to manage errors across our entire application? That's where Global Exception Handling comes in. It allows us to define a consistent error response format and avoid repetitive error handling code in each controller.
Why Global Exception Handling?
- Centralized Error Management: Handle all exceptions in one place, making maintenance easier.
- Consistent Error Responses: Provide a uniform error format to clients, improving the developer experience.
- Reduced Code Duplication: Avoid repeating the same error handling logic in multiple controllers.
- Improved Readability: Controllers become cleaner and focus on business logic.
- Security: Control what information is exposed to the client in error responses.
Implementing Global Exception Handling
We achieve global exception handling using the @ControllerAdvice annotation. This annotation tells Spring that a class is responsible for handling exceptions thrown by controllers.
1. Create an Exception Handling Class:
Let's create a class called GlobalExceptionHandler.
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
// ... (Exception handling methods will go here) ...
}
@ControllerAdvice: Marks this class as a controller advice component.extends ResponseEntityExceptionHandler: Extending this class provides default exception handling for common Spring exceptions (likeMethodArgumentNotValidException). We can override these defaults if needed.
2. Define Exception Handling Methods:
Inside GlobalExceptionHandler, we define methods annotated with @ExceptionHandler. These methods will be invoked when the specified exception type is thrown within a controller.
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Map<String, String>> handleResourceNotFound(ResourceNotFoundException ex) {
Map<String, String> errorMap = new HashMap<>();
errorMap.put("error", "Resource Not Found");
errorMap.put("message", ex.getMessage());
return new ResponseEntity<>(errorMap, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, String>> handleIllegalArgumentException(IllegalArgumentException ex) {
Map<String, String> errorMap = new HashMap<>();
errorMap.put("error", "Invalid Argument");
errorMap.put("message", ex.getMessage());
return new ResponseEntity<>(errorMap, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class) // Catch-all for other exceptions
public ResponseEntity<Map<String, String>> handleGlobalException(Exception ex) {
Map<String, String> errorMap = new HashMap<>();
errorMap.put("error", "Internal Server Error");
errorMap.put("message", "An unexpected error occurred.");
return new ResponseEntity<>(errorMap, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(ExceptionType.class): Specifies the exception type this method handles.ResponseEntity<Map<String, String>>: We return aResponseEntityto control the HTTP status code and the response body. The response body is aMapcontaining error details. You can customize this to use a more complex error object if desired.HttpStatus: Sets the appropriate HTTP status code for the error.ex.getMessage(): Retrieves the exception message. Be careful about exposing sensitive information in the message.
3. Custom Exception Classes (Optional but Recommended):
For better organization and clarity, it's good practice to define custom exception classes. Let's create a ResourceNotFoundException:
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
4. Using the Custom Exception in a Controller:
@RestController
@RequestMapping("/products")
public class ProductController {
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
// Simulate a resource not found scenario
if (id == 1) {
throw new ResourceNotFoundException("Product with ID 1 not found");
}
return new Product(id, "Example Product");
}
}
5. Testing the Global Exception Handling:
Now, if you send a request to /products/1, the ResourceNotFoundException will be thrown. The GlobalExceptionHandler will catch it, and return a JSON response like this:
{
"error": "Resource Not Found",
"message": "Product with ID 1 not found"
}
with an HTTP status code of 404 (NOT_FOUND).
Key Considerations:
- Exception Hierarchy: Order your
@ExceptionHandlermethods from specific to general. The most specific exception type should be handled first. TheException.classhandler should be last as a catch-all. - Logging: Always log exceptions for debugging and monitoring purposes. Use a logging framework like Logback or Log4j.
- Security: Avoid exposing sensitive information in error messages. Consider using generic error messages for production environments.
- Error Response Format: Design a consistent error response format (e.g., JSON with
error,message,codefields) for your API. ResponseEntityExceptionHandler: Leverage the built-in exception handling provided byResponseEntityExceptionHandlerfor common Spring exceptions. Override methods as needed to customize the behavior.- @Valid and Exception Handling: When using
@Validfor request body validation,MethodArgumentNotValidExceptionis thrown.ResponseEntityExceptionHandlerprovides a default handler for this, but you can override it in yourGlobalExceptionHandlerto customize the error response.
This provides a solid foundation for implementing global exception handling in your Spring Boot applications. Remember to tailor the error responses and logging to your specific application requirements.