Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 40

Custom Exceptions for HTTP Errors

Recommended resources

Hello! Welcome back.

In our last lesson, we built a robust mechanism for centralizing exception handling using @ControllerAdvice and @ExceptionHandler. You learned how to intercept exceptions globally, preventing error-handling logic from cluttering your controllers.

Today, we'll build on that foundation to address the learning outcome: Define custom exception classes and map them to specific HTTP error responses. We will shift our focus from the mechanism of catching exceptions to the design of the exceptions themselves. Your goal is to create a clear and semantic error model for your API, where specific business rule violations are represented by unique exception types, each mapped to a precise and informative client response.

For someone with your background in system design, the importance of a well-defined API contract—including its error states—is self-evident. This lesson will give you the tools to implement such a contract within the Spring ecosystem.

1. The Need for Custom Exceptions

So far, we might throw a generic RuntimeException or rely on a NullPointerException to signal that something went wrong, like a requested resource not being found. This is problematic for two reasons:

  1. Lack of Semantic Meaning: A NullPointerException doesn't tell you why something was null. Was it because a database record was missing, or because of a programming error? Code should be self-documenting, and generic exceptions obscure the business context of an error.
  2. Inability to Differentiate: If multiple distinct error conditions all result in the same generic exception, it's impossible for our @ControllerAdvice to know which specific HTTP status and error message to return to the client.

The solution is to create our own exception classes that represent specific failure scenarios in our application's domain, such as ResourceNotFoundException, InvalidOrderStateException, or InsufficientFundsException.

Let's start by seeing how to define and throw a simple custom exception.

Spring Boot 4 REST API Tutorial 🚀 | CRUD, Exception Handling & Swagger

The video 'Spring Boot 4 REST API Tutorial' from the Selenium Express channel clearly demonstrates creating a custom exception to handle a specific scenario.

Watch the segment from 00:57:02 to 00:59:13. The instructor creates a ResourceNotFoundException that extends RuntimeException and then throws it from the service layer when a user is not found. Pay attention to why RuntimeException is chosen as the base class.

As you saw, creating a custom exception in Java is straightforward. A key design choice is whether to extend Exception (a checked exception) or RuntimeException (an unchecked exception). In modern API development with Spring, the strong convention is to use unchecked exceptions. This avoids forcing every method in the call stack to declare throws ..., which can lead to significant code clutter. Since our @ControllerAdvice will handle these exceptions at the application boundary, we don't need the compiler to enforce handling at every level.


2. Designing a Consistent Error Response

Before we can map our new exception to a response, we need to define the structure of that response. A well-designed API returns errors in a consistent, predictable format. A common practice is to create a Data Transfer Object (DTO) specifically for error responses.

This DTO typically includes:

  • A timestamp
  • The HTTP status code
  • A short, machine-readable status string (e.g., "NOT_FOUND")
  • A human-readable message explaining the error
  • The request path that caused the error

Let's look at how to create a modern and concise error DTO using a Java record.

Spring Boot 4 REST API Tutorial 🚀 | CRUD, Exception Handling & Swagger

Let's continue with the Selenium Express video. The instructor defines a custom APIError structure that will be used for all error responses.

Watch from 00:49:51 to 00:51:58. This part introduces a Java record named APIError to serve as the structured error response. Records are a feature in modern Java that provide a compact syntax for creating immutable data-carrier classes.

Using a dedicated APIError record or class ensures that all clients of your API can rely on a single, consistent format for processing errors, regardless of what caused them.


3. Mapping Custom Exceptions in the @ControllerAdvice

Now we can connect the dots. The flow is as follows:

  1. The service layer throws our custom ResourceNotFoundException.
  2. The exception propagates up to the web layer.
  3. Our global @RestControllerAdvice intercepts it.
  4. A specific @ExceptionHandler method for ResourceNotFoundException is invoked.
  5. This handler method creates our APIError object and returns it with a 404 Not Found status.

The following video segment puts all these pieces together.

Spring Boot 4 REST API Tutorial 🚀 | CRUD, Exception Handling & Swagger

This segment from the Selenium Express video demonstrates the implementation of the handler method in the global advice class.

Watch from 00:51:58 to 00:56:26. Observe how an @ExceptionHandler method is created. It takes the exception and the HttpServletRequest as parameters, uses them to construct the APIError record, and wraps it in a ResponseEntity with the HttpStatus.NOT_FOUND status.

This pattern is extremely powerful. You can add as many custom exception types as your application needs and provide a dedicated handler for each one within your single @RestControllerAdvice class.

For example, a very common requirement is to handle input validation failures. When a client sends an invalid request body, Spring throws a MethodArgumentNotValidException. We can easily add a handler for this to return a 400 Bad Request with details about which fields failed validation.

Spring Boot 4 REST API Tutorial 🚀 | CRUD, Exception Handling & Swagger

To see how versatile this approach is, let's watch one more clip from the same video showing how to handle a different category of error: validation.

Watch from 01:19:01 to 01:25:03. The instructor adds validation annotations (@Valid) to the controller, then adds a new @ExceptionHandler for MethodArgumentNotValidException to the global advice. Notice how this handler extracts field-specific error messages from the exception to provide a highly detailed response to the client.


4. An Alternative: The @ResponseStatus Annotation

For simple cases where you only need to map an exception to an HTTP status code without a custom response body, Spring offers a more direct approach: the @ResponseStatus annotation.

You can place this annotation directly on your custom exception class.

Error Handling for REST with Spring

The Baeldung article 'Error Handling for REST with Spring' provides a clear explanation of this alternative.

Please read section 3, 'Annotate Exceptions Directly'. It shows how to add @ResponseStatus(value = HttpStatus.NOT_FOUND) directly to a MyResourceNotFoundException class.

The primary trade-offs for using @ResponseStatus are:

  • Pros: It's very simple and requires no extra handler methods.
  • Cons: It's less flexible. By default, the response body will be null. It also tightly couples your exception class to a single HTTP status, whereas the @ExceptionHandler approach allows for more context-sensitive logic.

In general, for building robust and informative APIs, the @ControllerAdvice approach is preferred. The @ResponseStatus annotation is best reserved for rapid prototyping or very simple internal applications.

The diagram below summarizes the recommended approach, highlighting the central role of @RestControllerAdvice and @ExceptionHandler.

Custom Error Handling in Spring Boot REST Controllers
This diagram contrasts different error handling strategies in Spring Boot. The green checkmarks indicate the recommended practice: using a class annotated with `@RestControllerAdvice` containing `@ExceptionHandler` methods to create custom, consistent error responses.

Conclusion

In this lesson, you learned how to design and implement a semantic error model for a Spring REST API. By defining custom exceptions, you make your code more expressive and enable your global exception handler to provide precise, helpful responses to API clients.

Key Takeaways:

  • Create custom exceptions (e.g., ResourceNotFoundException) that extend RuntimeException to represent specific business rule failures.
  • Define a standard DTO (like a class or record) for your error responses to ensure a consistent API contract.
  • The most flexible way to map an exception to a response is by creating a dedicated @ExceptionHandler method within your global @RestControllerAdvice.
  • This handler method should construct your error DTO and return it inside a ResponseEntity with the appropriate HttpStatus.
  • For simpler cases, you can annotate the exception class itself with @ResponseStatus to map it directly to an HTTP status code, though this offers less control over the response body.

Now that you can effectively catch and report errors, the next logical step is to record them for debugging and monitoring. In our next lesson, we will cover the learning outcome: Configure and use SLF4J with Logback for structured application logging.