skip to content

What is @Filter in Spring Integration, and how does it differ from a router and a transformer?

level: middleimportance: should knowfreq 45%

answer

  1. boolean gate: true pass unchanged, false drop
  2. MessageFilter + MethodInvokingSelector
  3. discardChannel = don't lose rejects
  4. throwExceptionOnRejection = MessageRejectedException
  5. 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 s

A 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 lines
java
import 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

for a junior

Know it returns true/false to keep or drop a message unchanged.

for a middle

Know discardChannel and throwExceptionOnRejection, MessageSelector, and the filter-vs-router-vs-transformer contrast.

for a senior

Use selectors/SpEL, place filters to shed load early, and make rejections observable for ops.

for a principal

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.

context