Overview
@Filter performs application-side filtering in Java. All events arrive from MongoDB, and a
predicate decides which ones are forwarded to handler methods. Events that don’t pass the filter
are silently skipped — a rejection is a terminal settlement, so their resume token still
advances the lastProcessedToken checkpoint anchor.
Unlike @Pipeline which runs a MongoDB aggregation pipeline once at
stream startup to reduce network traffic, @Filter executes on every event and can leverage
Spring beans, service calls, and any Java logic.
At most one
@Filter method is allowed per @ChangeStream class.Supported Signatures
Basic Example
Startup Validation (Fail-Fast)
FlowWarden validates@Filter compatibility at application startup and rejects clearly
incompatible configurations immediately with a clear error message. The application will fail
to start if @Filter is combined with a typed handler covering an operation where MongoDB
does not provide a fullDocument.
Why?
@Filter predicates typically call ctx.getFullDocument(), which returns Optional.empty() for
DELETE, DROP, and INVALIDATE operations. Running the filter on a typed handler dedicated to
one of those operations is almost always a mistake, so FlowWarden rejects that combination at
startup. Combining @Filter with the @OnChange catch-all is allowed — DROP and INVALIDATE will
reach the predicate, and the predicate must handle Optional.empty() explicitly.
Rule
The following combination is rejected:@Filter + typed handler for a no-fullDocument operation
Valid Combinations
The following combinations are accepted:Quick Reference
Checkpoint Interaction
A rejection by@Filter is a terminal settlement: the event’s outcome is decided, so its
token advances lastProcessedToken exactly like a handler success does (per the
saveEveryN / saveIntervalSeconds policy). A filter-heavy stream therefore stays recoverable
at resume cascade level 1 through its rejected traffic:
rejections advance the anchor instead of stalling it. As with any settlement, a restart re-reads
at most the events since the last confirmed write (up to N-1 with saveEveryN = N) — those
rejections are simply re-evaluated by the filter.
Rejected events never touch lastSeenToken, which holds exclusively server-certified positions
written by the idle heartbeat (idleHeartbeatIntervalSeconds) — the safety net for periods where
the stream settles nothing at all.
Combining with @Pipeline
@Filter can coexist with @Pipeline on the same @ChangeStream,
forming a double filtering funnel:
Typical use case: pre-filter operationType = insert | update and status = PAID
server-side with @Pipeline, then verify application-side with @Filter that the tenant
is active via a Spring service call.
When combining
@Pipeline + @Filter, events that pass the server-side pipeline reach
FlowWarden and settle terminally regardless of whether @Filter accepts them — accepted or
rejected, they advance the lastProcessedToken anchor. When the pipeline filters out
everything for a while, the idle heartbeat takes over and certifies fresh positions into
lastSeenToken, preserving the resume cascade
level-2 safety net.See Also
@Pipeline
Server-side aggregation pipeline filtering
Filtering Events Guide
Complete guide combining @Pipeline and @Filter
@Checkpoint
Resume token persistence and dual checkpoint
Event Handlers
@OnInsert, @OnUpdate, @OnDelete, @OnChange