When building REST APIs, handling exceptions in a consistent and meaningful way is crucial for a good user experience and maintainable code. Without proper exception handling, users might encounter vague error messages, or even worse, your API might crash unexpectedly.
Custom Exceptions
In a REST API, exceptions represent errors that occur during request processing. While Spring Boot provides built-in exceptions like ResourceNotFoundException
or BadRequestException
, creating custom exceptions tailored to your application makes your code more readable and maintainable.
Implementation of Custom Exceptions
- Define the Exception Class: Create a custom exception class by extending
RuntimeException
or any other exception class. Typically, you’d want to extendRuntimeException
because it is an unchecked exception, meaning it doesn’t require explicit handling in the method signature.
1 2 3 4 5 |
public class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException(String message) { super(message); // Pass the error message to the superclass } } |
- Throw the Custom Exception: Whenever a specific condition occurs (e.g., a resource isn’t found), you can throw this exception.
1 2 3 4 5 6 7 8 |
@GetMapping("/users/{id}") public ResponseEntity<User> getUserById(@PathVariable("id") Long id) { User user = userService.findById(id); if (user == null) { throw new ResourceNotFoundException("User not found with ID: " + id); } return ResponseEntity.ok(user); } |
Use Cases
- Clarity: By naming your exception based on the problem, it’s easier to understand what went wrong.
- Flexibility: You can pass additional details (such as error codes) to the exception, allowing better error handling.
- Customization: You can add extra methods (e.g., for error codes, timestamps, or detailed error messages) that would be useful in your API response.
Global Exception Handling with @ControllerAdvice
While custom exceptions help in identifying specific errors, handling them across your entire application can become cumbersome if you don’t centralize the logic. Instead of handling each exception in every controller, Spring Boot provides a convenient way to catch exceptions globally using the @ControllerAdvice
annotation.
@ControllerAdvice
is a specialized version of @Component
in Spring. It allows you to define global exception handling logic for all controllers. You can intercept exceptions and provide a uniform response to the API consumers.
Implementation of Global Exception Handling
-
Create the Global Exception Handler Class: Use
@RestControllerAdvice
to define a global exception handler. This will capture exceptions thrown from any controller.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) { // Create a custom error response object ErrorResponse errorResponse = new ErrorResponse("RESOURCE_NOT_FOUND", ex.getMessage()); return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); } @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) { ErrorResponse errorResponse = new ErrorResponse("INTERNAL_SERVER_ERROR", "An unexpected error occurred"); return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); } } |
- Define a Custom Error Response Class: It’s helpful to create a custom error response format that can be sent in case of an exception.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class ErrorResponse { private String errorCode; private String message; // Constructor, Getters, and Setters public ErrorResponse(String errorCode, String message) { this.errorCode = errorCode; this.message = message; } public String getErrorCode() { return errorCode; } public String getMessage() { return message; } } |
- Handling Other Exceptions: You can add more handlers for different types of exceptions, such as
MethodArgumentNotValidException
for validation failures.
1 2 3 4 5 6 |
@ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) { String errorMessage = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage(); ErrorResponse errorResponse = new ErrorResponse("VALIDATION_ERROR", errorMessage); return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); } |