DEV Community

Ed Legaspi
Ed Legaspi

Posted on • Originally published at czetsuyatech.com

Stop Rebuilding Exception Handling in Every Spring Boot Project

Discover nerv-exception
After working on multiple Spring Boot projects over the years, I noticed an interesting pattern.

Every application had different business requirements.

But almost every application ended up with the same exception handling infrastructure.

I found myself recreating the same classes over and over again:

  • @RestControllerAdvice
  • Custom exceptions
  • Error response models
  • Error codes
  • Validation handlers
  • Feign error decoders
  • Utility classes
  • Configuration

The business logic changed.

The exception handling didn't.

Eventually I asked myself:

Why am I rebuilding this for every project?

That question became nerv-exception.

Instead of copying hundreds of lines of exception handling infrastructure from project to project, I wanted a reusable, production-ready library that could become the foundation for every Spring Boot application.


The Problem

A typical Spring Boot application usually starts with something like this.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ApiError> handle(UserNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(new ApiError(
                        "USER_NOT_FOUND",
                        ex.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiError> handle(MethodArgumentNotValidException ex) {
        ...
    }

}
Enter fullscreen mode Exit fullscreen mode

Looks simple.

Until the application grows.

Now you have:

  • business exceptions
  • validation exceptions
  • security exceptions
  • Feign client exceptions
  • Kafka consumer exceptions
  • asynchronous processing
  • scheduled jobs

Every new integration introduces another place where exceptions need to be translated.

Soon enough, different parts of your application return completely different error payloads.

Your REST API becomes inconsistent.


What I Wanted

I wasn't trying to replace Spring Boot.

Spring Boot already provides excellent exception handling capabilities.

Instead, I wanted a reusable foundation that provides:

  • consistent API responses
  • centralized error codes
  • minimal boilerplate
  • RFC 7807 Problem Details
  • Spring Boot auto-configuration
  • modular architecture

Most importantly...

I wanted something I would actually use in production.


Introducing nerv-exception

The library is intentionally split into multiple modules.

nerv-exception-api
nerv-exception-core
nerv-exception-spring-web
nerv-exception-spring-boot-starter
nerv-exception-spring-feign
nerv-exception-event
nerv-exception-spring-kafka
Enter fullscreen mode Exit fullscreen mode

Each module has a single responsibility.

For example:

  • api contains only the public contracts.
  • core contains the domain exception model.
  • spring-web integrates with Spring MVC.
  • starter provides auto-configuration.
  • feign handles remote service errors.
  • event standardizes exception events.
  • spring-kafka integrates with Kafka consumers.

Applications only depend on what they actually need.


Adding the Library

Getting started is intentionally simple.

<dependency>
    <groupId>com.czetsuyatech</groupId>
    <artifactId>nerv-exception-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

That's it.

Spring Boot auto-configuration registers everything automatically.


Standardized Error Responses

One of the biggest goals of the project was consistency.

Instead of every controller returning its own custom JSON, APIs can return standardized RFC 7807 Problem Details.

Example:

{
  "type": "https://example.com/problems/user-not-found",
  "title": "User not found",
  "status": 404,
  "detail": "User 123 was not found.",
  "instance": "/users/123"
}
Enter fullscreen mode Exit fullscreen mode

This makes APIs significantly easier to consume.

Frontend developers know exactly what to expect.

API documentation becomes simpler.

Clients become easier to implement.


Designed for Extension

One design decision I made very early was:

Avoid forcing applications into one way of doing things.

The library provides sensible defaults while remaining extensible.

For example:

  • custom error codes
  • custom exception mapping
  • trace context propagation
  • Feign integration
  • Kafka integration
  • event publishing

Applications can override behavior without modifying the library itself.


Runnable Examples

Documentation is helpful.

Runnable code is even better.

That's why I also created a companion repository containing complete Spring Boot applications demonstrating how to use the library in real projects.

Rather than reading isolated snippets, developers can clone the repository, run the application, and explore the implementation themselves.

Repository:

https://github.com/czetsuyatech/nerv-examples


Why Open Source?

Throughout my career I've benefited enormously from open source.

Many libraries have saved me countless hours.

Building nerv-exception is my opportunity to give something back.

If another developer no longer has to rebuild the same exception handling infrastructure for their next project, then this library has already accomplished its goal.


Resources

GitHub

https://github.com/czetsuyatech/nerv-exception

Maven Central

<dependency>
    <groupId>com.czetsuyatech</groupId>
    <artifactId>nerv-exception-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Example Projects

https://github.com/czetsuyatech/nerv-examples


I'm continuously improving the library based on real-world experience.

If you have ideas, suggestions, or find it useful, I'd love to hear your feedback.

Happy coding! 🚀

Top comments (0)