@RestartPolicy annotation (since 1.0.0-rc.7) tunes the managed restart loop of a stream: how long FlowWarden waits between resubscription attempts after the stream died at runtime, and whether it ever gives up. Place it on a class alongside @ChangeStream.
Without the annotation, every stream uses the defaults below — which are exactly the behaviour FlowWarden had before the annotation existed. Nothing changes for an application that does not use it.
What the restart loop does
A stream can die at runtime, after it started successfully: the cursor is closed by a network outage or a primary stepdown, the server returns a non-resumable error, or the watched collection is dropped or renamed (aninvalidate event). In every case FlowWarden:
- evicts the stream’s state and reports it stopped —
onStreamStopped(CRASHED)on the metrics provider; - hands the stream to the restart loop, which re-enters the full startup path (resume cascade included) after a backoff delay;
- on success, reports
onStreamRestarted(streamName, attempt, cause)and resets the attempt counter; - on a transient failure (server still down, cascade probe failure), schedules the next attempt with a longer delay.
HistoryLostException escaping an attempt — the resume cascade escalated to onHistoryLost = FAIL — is always terminal: the loop stops for that stream, a second CRASHED signal carries the exception, and under SINGLE_LEADER the lease is released so a standby can take over.
A manual startStream (actuator or FlowWarden API) during the backoff wins over the loop: the installed stream is left alone.
Restart attempts run on one dedicated thread per manager (fw-stream-restart / fw-stream-restart-reactive), so a slow resubscription never delays heartbeat probes or checkpoint flushes of healthy streams.
Attributes
Two deliberate differences from
@RetryPolicy:maxAttemptscounts resubscription attempts — there is no “initial attempt” to include — and0means unlimited, which is the default.@RetryPolicy.maxAttemptsmust be>= 1.jitterdefaults tofalse, so a stream without the annotation keeps the deterministic cadence it always had.@RetryPolicy.jitterdefaults totrue.
Backoff Algorithm
Same shape as@RetryPolicy:
The counter resets on the first successful resubscription: a stream that dies again later starts over at attempt 1.
Giving up
WhenmaxAttempts is set and exhausted, the stream takes the same terminal path as a history loss:
- the stream is reported stopped —
onStreamStopped(CRASHED)with the original death cause; - the loop stops for that stream — no further attempts, no further
WARNlines; - under
SINGLE_LEADER, the lease is released so another instance can take over (and surface the same failure if the cause is global); - the stream stays stopped until an operator restarts it —
POST /actuator/flowwarden/{streamName}/restartorFlowWarden.startStream(..).
When to set it
Validation
Validated fail-fast at startup, on both the annotation path and the registration API:maxAttempts >= 0multiplierfinite and>= 1— a shrinking cadence would degenerate into a hot loop against a down server;NaNand infinities are rejected rather than silently collapsing tomaxDelayinitialDelayandmaxDelayparse (<number>ms|s|m) and are strictly positive — same reason
BeanCreationException naming the attribute.
Programmatic equivalent
RestartPolicySpec on StreamSpec.Builder.restartPolicy(..), same defaults:
See Also
@RetryPolicy
Retry of a failed handler invocation — per event, not per stream
@Checkpoint
Resume cascade and the
onHistoryLost terminal path the loop sharesStreamMetricsProvider
onStreamStopped(CRASHED) and onStreamRestarted signalsStreamSpec
RestartPolicySpec for contributed streams