Skip to main content
The @RetryPolicy annotation enables automatic retry with exponential backoff for failed handler invocations. When placed on a class alongside @ChangeStream, the framework retries the handler up to maxAttempts times with increasing delays between attempts.

Attributes

maxAttempts includes the initial invocation. So maxAttempts = 3 means 1 initial attempt + 2 retries.

noRetryOn Defaults

By default, the following exceptions skip retry entirely — they are considered programming errors that retrying won’t fix:
  • IllegalArgumentException
  • NullPointerException
  • ClassCastException
noRetryOn always takes precedence over retryOn.

Backoff Algorithm

The delay between retries is computed using exponential backoff with an optional jitter:
Using the defaults (initialDelay = "500ms", multiplier = 2.0, maxDelay = "30s") and disabling jitter to show pure exponential growth, the delays are:

Head-of-Line Blocking

@RetryPolicy executes retries on the stream’s processing thread. While a handler is being retried, FlowWarden does not advance to the next event — the entire stream is held until the current event has succeeded, exhausted all retries, or returned ErrorAction.SKIP / ErrorAction.DLQ from an @OnError handler. A long retry sequence therefore blocks the stream for the sum of all backoff intervals (e.g. ~15 s with maxAttempts = 5, initialDelay = "1s", multiplier = 2.0). This is a deliberate trade-off preserving strict in-order delivery. See the retry guide for a worked example and the available workarounds.

See Also

Retry & DLQ Guide

Understand retry flows, exception filtering, tracking attempts, and best practices

@DeadLetterQueue

Store events that exhaust all retries for later reprocessing

@Checkpoint

Resume token persistence for reliable stream recovery

ChangeStreamContext

Runtime context including attempt number, event ID, and more