What is @Filter in Spring Integration, and how does it differ from a router and a transformer?
answer
- boolean gate: true pass unchanged, false drop
- MessageFilter + MethodInvokingSelector
- discardChannel = don't lose rejects
- throwExceptionOnRejection = MessageRejectedException
- never mutates payload (that's a transformer)
basics
~20 s@Filter marks a method returning a boolean: true forwards the message unchanged to the output channel, false drops it. It's a keep-or-drop gate — unlike a router (which picks among destinations) or a transformer (which changes the message).
solid answer
~40 sA message filter decides whether a message should continue in the flow. You annotate a boolean-returning method with @Filter(inputChannel="in", outputChannel="out"); returning true passes the message through *unchanged*, false discards it. The framework wraps it in a MessageFilter with a MethodInvokingSelector (or you can supply a MessageSelector/ExpressionEvaluatingSelector directly). Two important options: discardChannel routes rejected messages somewhere (audit/dead-letter) instead of dropping them, and throwExceptionOnRejection=true turns a rejection into a MessageRejectedException rather than a silent drop. The method may take the Message or just the payload. Key contrast: a filter is binary keep/drop and never mutates the payload; a transformer always mutates and always emits; a router selects among many channels. Use filters to enforce preconditions or drop irrelevant traffic early.
code
java · 21 linesimport org.springframework.integration.annotation.Filter;
import org.springframework.messaging.Message;
public class OrderFilters {
// Rejected messages go to an audit channel instead of vanishing;
// and a rejection also throws so an error flow can react.
@Filter(inputChannel = "incomingOrders",
outputChannel = "validOrders",
discardChannel = "rejectedOrders",
throwExceptionOnRejection = "true")
public boolean isProcessable(Message<Order> msg) {
Order o = msg.getPayload();
return o.getAmount() > 0 && o.getCustomerId() != null;
}
}
class Order {
double getAmount() { return 1; }
String getCustomerId() { return "c1"; }
}go deeper
Know it returns true/false to keep or drop a message unchanged.
Know discardChannel and throwExceptionOnRejection, MessageSelector, and the filter-vs-router-vs-transformer contrast.
Use selectors/SpEL, place filters to shed load early, and make rejections observable for ops.
Design rejection handling as first-class (dead-letter/audit), avoid silent loss across the topology, and reason about idempotency/back-pressure implications of dropping.
## The Message Filter pattern A **filter** is the EIP endpoint that acts as a **gate**: for each incoming message it decides **keep or drop**. It has one input and *zero-or-one* output — the message either passes through **unchanged** to the output channel or is rejected. It never alters the payload (that's a transformer) and never chooses among multiple destinations (that's a router). ## Declaring with @Filter ```java @Filter(inputChannel = "in", outputChannel = "valid") public boolean accept(Order order) { return order.getAmount() > 0; // true = pass, false = drop } ``` The method **must return a boolean** (or Boolean). Internally the framework builds a `MessageFilter` backed by a `MethodInvokingSelector`; the selector's boolean result gates the send. Method input can be the full `Message<?>` or just the payload. ## What happens to rejected messages — the key options By default a rejected message is **silently dropped**. Two options change that: - **`discardChannel`** — rejected messages are sent here instead of being discarded, enabling audit trails, dead-letter handling, or alternate processing. - **`throwExceptionOnRejection = true`** — rejection raises a `MessageRejectedException` instead of dropping, so callers/error channels can react. You can combine: send to `discardChannel` *and/or* throw, depending on whether a drop should be observable. ## Selectors (the reusable form) Instead of a method you can implement `MessageSelector` (`boolean accept(Message<?>)`) and hand it to a `MessageFilter`, or use `ExpressionEvaluatingSelector` with a SpEL boolean expression. The Java DSL exposes `.filter(...)`: ```java .filter(Order.class, o -> o.getAmount() > 0, f -> f.discardChannel("rejectedOrders")) ``` ## Filter vs router vs transformer (the classic interview contrast) | Endpoint | Inputs→Outputs | Mutates payload? | Decision | |---|---|---|---| | **Transformer** | 1 → 1 (always emits) | Yes | reshape | | **Filter** | 1 → 0 or 1 (unchanged) | No | keep/drop | | **Router** | 1 → 1..n (unchanged) | No | which destination(s) | ## Gotchas - **Silent loss**: default rejection just drops. In production set `discardChannel` (or throw) so you can observe/measure rejections. - **Don't reshape in a filter**: the passed-through message is the *original*; a filter returning true does not let you also modify it. Reshape with a transformer either side. - **Boolean only**: returning a non-boolean/`null` is a configuration error. - **Throughput placement**: filter early to shed irrelevant load before expensive stages.
- Where do rejected messages go by default, and how do you capture them?By default they're silently dropped. Set discardChannel to route rejects (audit/dead-letter), and/or throwExceptionOnRejection=true to turn a rejection into a MessageRejectedException.
- Can a filter both drop and modify a message?No. A filter only decides keep/drop and passes the original message unchanged when kept. To modify, use a transformer before or after the filter.
saying these in an interview costs you the question
- Claiming a filter can transform the payload when it passes it through.
- Assuming rejected messages are logged/kept by default — they're silently dropped unless you set discardChannel or throw.
- Returning something other than boolean from a @Filter method.