@Checkpoint annotation enables automatic persistence of MongoDB Change Stream resume tokens with at-least-once delivery guarantees. FlowWarden tracks two independent tokens — lastSeenToken (advances on every event received, even ones rejected by @Filter) and lastProcessedToken (advances only on confirmed handler success) — and combines them in a 3-level resume cascade on restart.
Basic Usage
Attributes
Checkpoints are stored in the
_fw_checkpoints collection in your MongoDB database. The collection name is not configurable.saveEveryN
Controls how often lastProcessedToken is persisted, based on successful handler invocations. Setting this to a higher value reduces write pressure on MongoDB at the cost of potentially replaying more events on crash recovery (cascade level 1).
saveIntervalSeconds
A periodic timer that persists only lastSeenToken at the specified interval, even when no events are arriving and even for events rejected by @Filter. This is the cascade level-2 safety net — it keeps the resume position fresh on idle streams or streams where saveEveryN rarely triggers.
startPosition
Determines where the stream starts consuming when it is first created or when no checkpoint exists.
Resume cascade
On restart withstartPosition = RESUME, FlowWarden applies a 3-level cascade to decide where to resume the Change Stream. Which token sits at each level depends on the resumeStrategy:
- PROCESSED_FIRST (default)
- SEEN_FIRST
startPosition = RESUME (the default). With startPosition = LATEST, both persisted tokens are ignored and the stream starts from the current moment.
onHistoryLost
Determines the recovery strategy at cascade level 3 — when neither lastProcessedToken nor lastSeenToken can resume the stream. With the cascade, reaching level 3 means the downtime exceeded the oplog window and the heartbeat timer was disabled (or also aged out), which is genuinely abnormal.
HistoryLostException
HistoryLostException
When The exception message includes the stream name, the last checkpoint timestamp, and suggestions for recovery strategies.
onHistoryLost = FAIL, the framework throws a HistoryLostException with an actionable message:resumeStrategy
Picks which of the two persisted tokens the cascade tries first. The other token remains the cascade fallback before onHistoryLost, so the dual-token safety net applies under both modes.
SEEN_FIRST still falls back to lastProcessedToken before escalating to onHistoryLost. To skip even that fallback and start from the current moment when the seen token is unusable, combine SEEN_FIRST with onHistoryLost = RESUME_FROM_NOW. The intermediate fallback to processed will still fire if it is valid — the RESUME_FROM_NOW only kicks in at level 3.Comprehensive Example
How It Works
FlowWarden uses a dual-token checkpoint model internally:
This separation enables at-least-once delivery guarantees: if the application crashes after receiving an event but before the handler succeeds, the stream resumes from
lastProcessedToken on the next start (cascade level 1) and the in-flight event is re-delivered.
- Crash mid-handler (cascade level 1)
- Idle / filtered stream (heartbeat)
- Long outage (cascade level 2 fallback)
Interaction with @Pipeline
Interaction with @Pipeline
When a
@Pipeline is present, it filters events server-side, creating a gap between the actual oplog position and the last event your handler processed. The lastSeenToken tracks the oplog position independently (via periodic sampling), so on restart, MongoDB resumes from the most recent position — not from the last processed event.Without this mechanism, a restart would force MongoDB to re-scan the entire oplog from lastProcessedToken, potentially scanning tens of thousands of filtered-out events.See the @Pipeline reference for details.Storage
Checkpoints are persisted via theCheckpointStore SPI. The MongoDB-backed implementation (MongoCheckpointStore / ReactiveMongoCheckpointStore) is auto-configured by default; a Redis-backed alternative ships in flowwarden-redis. Custom backends (JDBC, in-memory, …) plug in by declaring a @Bean CheckpointStore.
Best Practices
- Keep
saveEveryN = 1for critical streams (e.g., order processing, billing) to minimize event replay on crash. - Increase
saveEveryNfor high-throughput streams where replaying a few events is acceptable — this reduces checkpoint write pressure. - Do not disable
saveIntervalSecondsunless you have a specific reason. The heartbeat is the cascade level-2 safety net and the only mechanism that advanceslastSeenTokenon idle or filter-heavy streams. - Use
startPosition = LATESTonly for streams where historical events are irrelevant (e.g., cache invalidation). - Alert on
flowwarden.stream.resume.fallback_to_seen— underPROCESSED_FIRST, a recurring level-2 fallback signals thatlastProcessedTokenages out faster than the handler can drain its backlog, typically a sign of a stuck or retry-pathological handler. - Alert on
flowwarden.stream.resume.fallback_to_processed— underSEEN_FIRST, this fires only when the heartbeat-freshlastSeenTokenhas aged out, which is unusual and worth investigating (heartbeat timer disabled? saveIntervalSeconds too long? outage longer than the oplog window?). - Alert on
flowwarden.stream.resume.history_lost— reaching level 3 means downtime overran the oplog window with no usable fallback; investigate operationally rather than silencing it.
See Also
Checkpoint & Resume Guide
Step-by-step guide to configuring checkpoint and resume behavior
@ChangeStream
The main annotation for declaring Change Stream handlers
@Pipeline
Server-side filtering and its interaction with dual-token checkpointing
@RetryPolicy
Configure retry behavior for failed event processing