@OnError annotation marks a method as a custom error handler inside a @ChangeStream class. When a handler method throws an exception, FlowWarden consults @OnError methods before the standard retry/DLQ chain, giving you full control over what happens next.
Basic Usage
Method Signature
@OnError methods must follow this exact signature:
| Element | Requirement |
|---|---|
| Return type | ErrorAction (required) |
| First parameter | Throwable (or any subclass) |
| Second parameter | ChangeStreamContext<?> |
Attribute
| Attribute | Type | Default | Description |
|---|---|---|---|
value | Class<? extends Throwable>[] | {} | Exception types this handler matches. Empty = catch-all |
ErrorAction
The return value of an@OnError method tells FlowWarden what to do next:
| Action | Behavior |
|---|---|
SKIP | Ignore the event. Logs a warning, no retry, no DLQ. Stream continues. |
RETRY | Force a retry. Respects @RetryPolicy.maxAttempts if present. |
DLQ | Send directly to the Dead Letter Queue, bypassing any remaining retries. |
RETHROW | Let FlowWarden handle with the standard policy (retry chain → DLQ). |
Exception Filtering
Typed Handlers
Target specific exception types using thevalue attribute:
Multiple Exception Types
A single handler can match multiple exception types:Catch-All Handler
Omitvalue (or set it to {}) to create a catch-all that handles any unmatched exception:
Resolution Order
When multiple@OnError methods exist, FlowWarden picks the most specific match:
Resolution example
Resolution example
Given these handlers:
| Exception thrown | Handler called | Reason |
|---|---|---|
NullPointerException | onNpe | Exact match (distance 0) |
IllegalStateException | onRuntime | Parent match (RuntimeException) |
IOException | catchAll | No match for NPE or RuntimeException |
Comprehensive Example
@OnError methods always use the imperative signature — even in reactive streams. The reactive handler may return Mono.error(), but the error handler itself is synchronous and returns an ErrorAction directly.How It Works
Validation Rules
FlowWarden validates@OnError methods at startup and will throw a BeanCreationException if:
| Rule | Error |
|---|---|
Return type is not ErrorAction | ”must return ErrorAction” |
| Wrong parameter count or types | must have signature: ErrorAction method(Throwable ex, ChangeStreamContext<?> ctx) |
| Multiple catch-all handlers | ”has multiple catch-all @OnError methods. At most one is allowed” |
| Duplicate exception types | ”has duplicate @OnError for exception type: …” |
Error Handler Safety
If an@OnError method itself throws an exception, FlowWarden catches it, logs the error, and falls back to ErrorAction.RETHROW. This prevents error handlers from crashing the stream.
Best Practices
- Use
SKIPfor programming errors (validation failures, malformed data) that retrying won’t fix. - Use
RETRYfor known transient errors where you want to bypass the exception type checks of@RetryPolicy.noRetryOn. - Use
DLQto short-circuit retries when you can determine that an error is permanent (e.g., external service returns HTTP 404). - Use
RETHROWas a safe default in catch-all handlers — it lets the standard retry/DLQ chain do its job. - Keep error handlers simple. Avoid calling external services or performing database operations in
@OnErrormethods — they should be fast decision-makers, not processors.
See Also
@RetryPolicy
Configure exponential backoff retry for failed handlers
@DeadLetterQueue
Capture failed events for later investigation and reprocessing
Event Handlers
@OnChange, @OnInsert, @OnUpdate, @OnDelete handler reference
ChangeStreamContext
Runtime context including attempt number, event ID, and sendToDlq()

