Error Handling
In our previous lesson, we implemented the create task list functionality but left the error handling to be desired - when validation fails, raw exceptions are thrown to the client. Let's improve this by implementing proper error handling.
Creating the Error Response
First, let's create a record to represent our error responses. Create a new ErrorResponse record in the com.devtiro.tasks.domain package:
public record ErrorResponse(
int status,
String message,
String details
) { }Using a record here gives us several benefits:
- Automatic constructor creation
- Built-in getters
- Automatic equals, hashCode, and toString implementations
- Immutability by default
Implementing Global Exception Handling
Next, let's create a global exception handler that will catch exceptions from our controllers and convert them into appropriate HTTP responses. Create a new class GlobalExceptionHandler in the controllers package:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({IllegalArgumentException.class, IllegalStateException.class})
public ResponseEntity<ErrorResponse> handleIllegalExceptions(
RuntimeException ex,
WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
ex.getMessage(),
request.getDescription(false)
);
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
}Let's break down the key components:
-
Class Annotation:
@ControllerAdvicetells Spring this class handles exceptions across all controllers
-
Handler Method:
@ExceptionHandlerspecifies which exceptions this method handles- Takes both the exception and the web request as parameters
- Returns a
ResponseEntitycontaining our error response
How It Works
When an exception occurs in our application:
- The exception propagates up to Spring's exception handling mechanism
- Spring finds our
@ControllerAdviceclass - Spring matches the exception type to our handler method
- Our handler creates an ErrorResponse with:
- HTTP status code (400 for Bad Request)
- Exception message
- Request details
- Spring converts the ErrorResponse to JSON and sends it to the client
For example, if we try to create a task list without a title, instead of a raw error, the client will receive:
{
"status": 400,
"message": "Task list title must be present!",
"details": "uri=/task-lists"
}
Benefits of This Approach
-
Consistency:
- All errors follow the same format
- Clients know what to expect
-
Security:
- We control what information is exposed
- No internal exception details leak to clients
-
Clarity:
- Clear, purpose-built error messages
- Helpful details for debugging
-
Separation of Concerns:
- Error handling logic is centralized
- Controllers stay focused on happy paths
Summary
- Created an
ErrorResponserecord for consistent error representation - Implemented global exception handling
- Provided clear, secure error messages to clients
- Centralized error handling logic in one place