Explain the difference between .transform(), .filter(), .route(), and .handle() in the Integration Java DSL.
answer
- transform=map (must return)
- filter=drop (boolean, silent discard)
- route=branch (channelMapping/subFlowMapping)
- handle=do/terminate (null = one-way)
- transform null throws, filter false discards
basics
~20 stransform changes the payload and passes it on; filter drops messages that fail a boolean test; route sends messages to different channels/sub-flows based on a value; handle consumes the message to do work, often ending the flow.
solid answer
~40 sThese four map to core EIP endpoints. `.transform()` registers a **transformer**: it takes the incoming message payload, returns a new payload (or message), and forwards it — one-in, one-out, must return a value. `.filter()` registers a **filter**: a predicate returning boolean; true lets the message continue, false discards it (or throws if `throwExceptionOnRejection` is set). `.route()` registers a **router**: it computes a routing key/channel from the message and dispatches to one of several channels or sub-flows, so the flow branches. `.handle()` registers a **service activator / message handler**: it consumes the message to perform an action (call a bean, hit an outbound adapter); it may return a reply (continuing the flow) or be one-way/terminal. Rule of thumb: transform = map, filter = drop, route = branch, handle = do/terminate.
code
java · 18 lines@Bean
public IntegrationFlow orderFlow(OrderService service) {
return IntegrationFlow.from("orders")
.filter((Order o) -> o.total() > 0) // drop invalid
.transform((Order o) -> o.withStatus("RECEIVED"))// map payload
.<Order, String>route(Order::region, mapping -> mapping
.channelMapping("EU", "euChannel") // branch
.channelMapping("US", "usChannel")
.defaultOutputChannel("otherChannel"))
.get();
}
@Bean
public IntegrationFlow euFlow(OrderService service) {
return IntegrationFlow.from("euChannel")
.handle(service, "persistEuOrder") // do work / terminate
.get();
}go deeper
Should give the one-line role of each: map, drop, branch, do.
Should explain return-value semantics (transform must return, filter boolean, handle null = one-way) and discard behavior.
Should discuss ordering (filter before transform), discardChannel/throwExceptionOnRejection, and subFlowMapping for branching pipelines.
Should map each to its EIP and reason about designing composable flows, error routing, and when to prefer routers vs multiple filters.
All four are **endpoints** in a Spring Integration flow — objects that consume a `Message<?>` from an input channel and optionally produce output to the next channel. They correspond to classic Enterprise Integration Patterns. **`.transform(...)` — Transformer (message-translator pattern):** - Purpose: convert the payload or headers. One message in, exactly one message out. - Signatures: `.transform(payload -> newPayload)`, `.transform(String.class, s -> ...)` (typed), or reference a bean method `.transform(myBean, "methodName")`. - Must **return** a value; returning `null` from a transformer throws (it's treated as an error, unlike a filter). The result becomes the new payload and flows onward. - Built-in helpers exist, e.g. `Transformers.objectToJson()`, `Transformers.fromJson(...)`. **`.filter(...)` — Filter:** - Purpose: allow or drop a message based on a predicate. One message in, zero or one out. - Signature: `.filter(payload -> boolean)` or a bean/`MessageSelector`. - `true` → message continues; `false` → message is **silently discarded** by default. You can configure `.filter(p -> ..., e -> e.throwExceptionOnRejection(true))` to raise `MessageRejectedException`, or route rejects to a `discardChannel`/`discardFlow`. - A filter never changes the payload — only gates it. **`.route(...)` — Router:** - Purpose: **branch** the flow to different destinations based on the message. - Signature: `.route(payload -> key, mapping -> mapping.channelMapping("KEY", "someChannel"))` or `.route(Type.class, p -> p.getX(), m -> m.subFlowMapping(...))`. - The router computes a value and picks the matching channel/sub-flow; unmatched values can go to a default output or throw. Unlike filter/transform it does not process the payload — it decides *where* it goes. Content-based routing, payload-type routing, and recipient-list routing are all router variants. **`.handle(...)` — Service Activator / MessageHandler:** - Purpose: **invoke logic** — call a service method, or plug in an outbound adapter (HTTP, JDBC, file, Kafka). - Signature: `.handle(msg -> ...)`, `.handle(myBean, "method")`, or `.handle(Http.outboundGateway(...))` etc. - If the handler **returns a value**, that becomes a reply message and the flow can continue; if it returns `void`/`null` it is effectively **one-way/terminal**. Placing endpoints after a one-way handler that produces no output does nothing. - `.handle()` is the usual end of a linear flow. **Distinguishing gotchas:** - `transform` returning `null` = error; `filter` returning `false` = normal discard. Don't use a transformer to drop messages. - `handle` returning `null` silently terminates — a common source of "my flow stops" confusion when you actually wanted a reply. - `route` doesn't transform; if you need to both branch and process, route into sub-flows that each do their own transform/handle. - Ordering matters: filter early to avoid transforming messages you'll discard. **When to use which:** map data → transform; gate/validate → filter; content-based branching → route; call external system or terminate → handle.
- What happens if a transformer returns null versus a filter returning false?A transformer returning null throws an exception — a transformer must produce output. A filter returning false is a normal outcome: the message is discarded silently (or sent to a discardChannel / throws only if throwExceptionOnRejection is set).
- How do you make a router branch into different processing pipelines rather than just channels?Use .route(...) with subFlowMapping(key, subflow -> ...) so each key maps to an inline IntegrationFlow, letting each branch do its own transform/filter/handle instead of only naming a target channel.
saying these in an interview costs you the question
- Saying a filter can change the payload — it only gates messages
- Claiming a transformer may return null to drop a message (that throws; use filter instead)
- Thinking .route() also transforms the payload