@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:
IllegalArgumentExceptionNullPointerExceptionClassCastException
noRetryOn always takes precedence over retryOn.
Backoff Algorithm
The delay between retries is computed using exponential backoff with an optional jitter: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