A lightweight, production-ready exception handling framework for Spring applications with first-class support for HTTP APIs, OpenFeign, Kafka, distributed tracing, retry-aware error propagation, and standardized error contracts.
nerv-exception provides a consistent failure model across synchronous and asynchronous communication channels while remaining framework-agnostic at its core.
Most distributed systems suffer from inconsistent error handling:
- Different services return different error payloads
- Retry behavior is undocumented
- Feign clients lose downstream context
- Kafka failures are difficult to correlate
- Trace information is not consistently exposed
- Error codes are scattered across services
NERV Exception solves these problems through a unified error contract built around NervErrorCode.
- Centralized exception handling
- Strongly typed application error codes
- Standardized error responses
- Retry-aware error contracts
- Error categorization
- Extensible architecture
- Framework-agnostic core API
- Automatic MVC exception handling
- Consistent HTTP error payloads
- Automatic status mapping
- Distributed trace exposure
- Automatic error decoding
- Retry-aware exception conversion
- Downstream error preservation
- Custom error code registries
- Structured error events
- Dead-letter queue publishing
- Trace-aware error propagation
- Kafka header mapping
- Micrometer Tracing integration
- OpenTelemetry integration
- Distributed trace support
- Span-aware diagnostics
- Zero-configuration setup
- Auto-configuration
- Conditional integrations
- No component scanning
nerv-exception provides built-in handling for the core Spring Security exceptions:
| Spring Security Exception | Native Error Code | HTTP Status |
|---|---|---|
AuthenticationException |
AUTHENTICATION_FAILED |
401 Unauthorized |
AccessDeniedException |
ACCESS_DENIED |
403 Forbidden |
These exceptions represent the fundamental authentication and authorization semantics supported by Spring Security and are handled automatically by the library.
Newer versions of Spring Security introduce additional exception types such as AuthorizationDeniedException. Since these are framework-specific implementations rather than core security abstractions, they are not handled by the library by default.
If your application uses these newer APIs, register an application-specific exception handler with a higher precedence:
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SecurityExceptionHandler {
@ExceptionHandler(AuthorizationDeniedException.class)
public ResponseEntity<NervErrorResponse> handle(
AuthorizationDeniedException ex,
HttpServletRequest request) {
NervException nervException =
NervException.of(NativeNervErrorCodes.ACCESS_DENIED, ex);
return ResponseEntity
.status(HttpStatus.FORBIDDEN)
.body(NervErrorResponseMapper.toResponse(nervException, request));
}
}This approach keeps nerv-exception independent of framework-specific implementations while allowing applications to support newer Spring Security features without changing the library.
HTTP Request
↓
NervException
↓
NervExceptionHandler
↓
NervErrorResponse
↓
Feign
↓
NervFeignErrorDecoder
↓
RetryableException / NervDownstreamException
↓
Kafka
↓
NervErrorEvent
↓
DLQ
| Module | Description |
|---|---|
nerv-exception-api |
Core contracts and abstractions |
nerv-exception-core |
Base exceptions, mappers, native error codes |
nerv-exception-spring-web |
Spring MVC integration |
nerv-exception-spring-feign |
OpenFeign integration |
nerv-exception-event |
Error event model and event mapping |
nerv-exception-spring-kafka |
Kafka integration and DLQ publishing |
nerv-exception-spring-boot-starter |
Auto-configuration |
starter
├─ spring-web
│ └─ core
│ └─ api
│
├─ spring-feign
│ └─ core
│ └─ api
│
└─ spring-kafka
└─ event
└─ core
└─ api
NERV Exception never relies on package scanning.
No:
@EnableNervExceptionis required.
All beans are created through:
NervExceptionAutoConfigurationFeign, Kafka, and tracing support activate only when their dependencies are available.
Retryability belongs to the error code itself.
Tracing is provided by Micrometer/OpenTelemetry.
NERV Exception consumes trace information but does not implement a tracing system.
<dependency>
<groupId>com.czetsuyatech</groupId>
<artifactId>nerv-exception-spring-boot-starter</artifactId>
<version>${nerv-exception.version}</version>
</dependency>public enum PaymentErrorCode implements NervErrorCode {
PAYMENT_TIMEOUT(
"PAYMENT_TIMEOUT",
"Payment provider timed out",
504,
true,
"INTEGRATION"
),
PAYMENT_NOT_FOUND(
"PAYMENT_NOT_FOUND",
"Payment not found",
404,
false,
"BUSINESS"
);
}throw new NervException(PaymentErrorCode.PAYMENT_TIMEOUT);{
"code": "PAYMENT_TIMEOUT",
"message": "Payment provider timed out",
"status": 504,
"retryable": true,
"category": "INTEGRATION",
"traceId": "0af7651916cd43dd8448eb211c80319c",
"spanId": "b9c7c989f97918e1",
"path": "/payments/timeout",
"timestamp": "2026-06-23T00:00:00Z"
}The entire framework revolves around a single abstraction:
public interface NervErrorCode {
String code();
String message();
int status();
boolean retryable();
String category();
}Every error in the system derives from this contract.
Enable:
nerv:
exception:
web:
enabled: trueFeatures:
- Global exception handling
- Automatic status mapping
- Standardized responses
- Trace-aware diagnostics
No additional configuration is required.
Enable:
nerv:
exception:
feign:
enabled: trueFeatures:
- Automatic error decoding
- Retry-aware exception conversion
- Custom error code resolution
- Downstream error preservation
When a downstream service returns:
{
"code": "PAYMENT_TIMEOUT",
"retryable": true
}NervFeignErrorDecoder automatically converts the response into:
RetryableExceptionallowing retry frameworks such as:
- Resilience4j
- Spring Retry
- Feign Retryer
to retry the request.
Non-retryable errors become:
NervDownstreamExceptionRemote failures preserve:
- traceId
- spanId
- timestamp
- path
- details
allowing easier troubleshooting across service boundaries.
Applications can register custom error code enums.
@Bean
NervErrorCodeRegistry applicationErrorCodeRegistry() {
return new EnumNervErrorCodeRegistry(
PaymentErrorCode.values(),
CustomerErrorCode.values(),
OrderErrorCode.values()
);
}Resolution order:
Application Registry
↓
Native Registry
Duplicate error codes are detected during startup.
| Category | Definition | Typical HTTP Status |
|---|---|---|
| VALIDATION | The request is invalid and must be corrected before retrying. | 400 Bad Request, 422 Unprocessable Entity |
| BUSINESS | The request is valid but cannot be completed due to business or resource state. | 404 Not Found, 409 Conflict, 410 Gone, 422 Unprocessable Entity |
| SECURITY | Authentication or authorization failure. | 401 Unauthorized, 403 Forbidden |
| DEPENDENCY | Failure caused by a downstream service or infrastructure dependency. | 408 Request Timeout, 429 Too Many Requests, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout |
| SYSTEM | Unexpected application failure or configuration problem. | 500 Internal Server Error |
| Exception | Category | HTTP |
|---|---|---|
MethodArgumentNotValidException |
VALIDATION | 400 |
ConstraintViolationException |
VALIDATION | 400 |
HttpMessageNotReadableException |
VALIDATION | 400 |
IllegalArgumentException |
VALIDATION | 400 |
EntityNotFoundException |
BUSINESS | 404 |
DuplicateKeyException |
BUSINESS | 409 |
OptimisticLockException |
BUSINESS | 409 |
BusinessRuleViolationException |
BUSINESS | 422 |
BadCredentialsException |
SECURITY | 401 |
AccessDeniedException |
SECURITY | 403 |
FeignException.BadGateway |
DEPENDENCY | 502 |
FeignException.ServiceUnavailable |
DEPENDENCY | 503 |
SocketTimeoutException |
DEPENDENCY | 504 |
CannotGetJdbcConnectionException |
DEPENDENCY | 503 |
KafkaException |
DEPENDENCY | 503 |
NullPointerException |
SYSTEM | 500 |
IllegalStateException |
SYSTEM | 500 |
Configuration/startup errors |
SYSTEM | 500 |
Enable:
nerv:
exception:
kafka:
enabled: true
source: payment-service
dlq-topic-suffix: .dlqFeatures:
- Structured error events
- Dead-letter publishing
- Kafka header mapping
- Trace-aware diagnostics
{
"code": "PAYMENT_TIMEOUT",
"message": "Payment provider timed out",
"category": "INTEGRATION",
"retryable": true,
"source": "payment-service",
"traceId": "0af7651916cd43dd8448eb211c80319c",
"spanId": "b9c7c989f97918e1",
"parentEventId": "payment-timeout-failed",
"timestamp": "2026-06-23T00:00:00Z"
}| Header | Description |
|---|---|
nerv-trace-id |
Trace identifier |
nerv-span-id |
Span identifier |
nerv-source |
Originating service |
nerv-parent-event-id |
Parent event identifier |
nerv-error-code |
Error code |
nerv-error-category |
Error category |
NERV Exception integrates with Micrometer Tracing.
Supported providers include:
- OpenTelemetry
- Brave
- Custom Micrometer implementations
Tracing is exposed through:
public interface NervTraceContextResolver {
NervTraceContext current();
}Default implementation:
MicrometerNervTraceContextResolverFallback:
NoOpNervTraceContextResolverRecommended dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-opentelemetry</artifactId>
</dependency>Propagation uses standard OpenTelemetry headers:
traceparent
tracestate
No custom propagation configuration is required.
| Contract | Purpose |
|---|---|
NervErrorCode |
Application error codes |
NervErrorCodeRegistry |
Remote error code resolution |
NervTraceContextResolver |
Distributed tracing integration |
NervEventTraceContextResolver |
Event tracing integration |
- Java 21+
- Spring Boot 4.x
Optional:
- OpenFeign
- Apache Kafka
- Micrometer Tracing
- OpenTelemetry
Apache License 2.0