Exception Handling

We're almost ready to try out our new create task endpoint.

But first we need to handle validation exceptions.

Create the Error DTO

As a reminder, our REST API represents all errors in this standard format:

{ "error": "An error message" }

Let's create a DTO class to represent this format.

In com.devtiro.task.domain.dto, let's create the ErrorResponseDto class:

/** * Models the standard error format used by the REST API. * * @param error The error message. */ public record ErrorResponseDto(String error) {}

This simple record has only a single field, error, but that's all it needs!

Now we can handle exceptions, returning an ErrorResponseDto to the caller.

Create the Global Exception Handler

There's a common pattern for handling exceptions in a Spring Boot REST API. It's called the "global exception handler".

This class contains logic to handle specific exceptions, returning the appropriate REST API response.

Let's create the GlobalExceptionHandler class in the com.devtiro.task.controller package:

/** * Handles exceptions thrown by the service layer, * returning errors in a standardised format. * */ @RestControllerAdvice public class GlobalExceptionHandler { // TODO: Handle validation exception. }

The @RestControllerAdvice annotation tells the framework to look here for exception handling methods.

Let's add one to handle validation exceptions.

Handle Validation Exceptions

We can add validation annotations to a DTO and use the @Valid annotation in our REST API controller to "activate" them.

When a caller provides an invalid request body, the framework throws a MethodArgumentNotValidException.

It's our responsibility to handle this exception. Here's how we do it:

/** * Handles MethodArgumentNotValidException, * returning a standardised error response and an HTTP 400 BAD REQUEST. * This exception is thrown when a @Valid validation fails. * * @param ex the exception. * @return A standardised error response. */ @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ErrorResponseDto> handleValidationException( MethodArgumentNotValidException ex) { // Get the first validation error message String errorMessage = ex.getBindingResult().getFieldErrors().stream() .findFirst() .map(DefaultMessageSourceResolvable::getDefaultMessage) .orElse("Validation failed."); // Create an ErrorResponseDto using the error message. ErrorResponseDto errorDto = new ErrorResponseDto(errorMessage); // Return the ErrorResponseDto in the response body // with an HTTP 400 BAD REQUEST. return new ResponseEntity<>(errorDto, HttpStatus.BAD_REQUEST); }

You can think of the @ExceptionHandler annotation as the counterpart to the @RestControllerAdvice annotation. It's the @ExceptionHandler annotated method that tells the framework how to handle a specific type of exception.

In this case, we extract the first validation message from the MethodArgumentNotValidException, returning it as an ErrorResponseDto, with a HTTP 400 BAD REQUEST.

Now that we've handled validation exceptions, let's try creating a new task in the user interface!

Summary

  • Created the ErrorResponseDto class.
  • Created the GlobalExceptionHandler class.
  • Handled the MethodArgumentNotValidException.
© 2026 Devtiro Ltd. All rights reserved