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:

  1. Class Annotation:

    • @ControllerAdvice tells Spring this class handles exceptions across all controllers
  2. Handler Method:

    • @ExceptionHandler specifies which exceptions this method handles
    • Takes both the exception and the web request as parameters
    • Returns a ResponseEntity containing our error response

How It Works

When an exception occurs in our application:

  1. The exception propagates up to Spring's exception handling mechanism
  2. Spring finds our @ControllerAdvice class
  3. Spring matches the exception type to our handler method
  4. Our handler creates an ErrorResponse with:
    • HTTP status code (400 for Bad Request)
    • Exception message
    • Request details
  5. 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

  1. Consistency:

    • All errors follow the same format
    • Clients know what to expect
  2. Security:

    • We control what information is exposed
    • No internal exception details leak to clients
  3. Clarity:

    • Clear, purpose-built error messages
    • Helpful details for debugging
  4. Separation of Concerns:

    • Error handling logic is centralized
    • Controllers stay focused on happy paths

Summary

  • Created an ErrorResponse record for consistent error representation
  • Implemented global exception handling
  • Provided clear, secure error messages to clients
  • Centralized error handling logic in one place
© 2026 Devtiro Ltd. All rights reserved