skip to content

Transformers, Routers, Filters, Splitters & Aggregators

Transformers, routers, filters, splitters and aggregators are the pattern vocabulary of any integration flow, with correlation and release strategies making aggregation work. Interviewers ask you to design a flow from these parts rather than write imperative glue.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

explore

questions

5

What does @Transformer do in Spring Integration, and how does a transformer endpoint fit into a message flow?

level: juniorimportance: must knowfreq 60%

answer

  1. one in, one out, always a result
  2. MessageTransformingHandler wraps the method
  3. null = error (that's a filter's job)
  4. payload-unwrap vs full Message signature
  5. ObjectToJson / header enricher built-ins

basics

~20 s

@Transformer marks a method that takes an incoming message (or its payload) and returns a changed version — a new payload or modified headers. It sits between two channels, reading from one and writing the result to the other.

solid answer

~40 s

A transformer is an EIP endpoint that converts a message on the way through a flow — changing the payload type, enriching content, or adjusting headers — without doing business branching. In Spring Integration you annotate a bean method with @Transformer(inputChannel="in", outputChannel="out"); the framework wraps it in a MessageTransformingHandler. The method can accept the raw Message<?> or just the payload (the framework unwraps it), and can return a new payload or a full Message. Returning null from a transformer throws an error — transformers must produce output (unlike filters, which may drop). Common built-ins include ObjectToJsonTransformer, JsonToObjectTransformer, and header enrichers. Key idea: one input, one output, always a result. Use it to adapt data between stages so downstream components see the shape they expect.

code

java · 24 lines
java
import org.springframework.integration.annotation.Transformer;
import org.springframework.messaging.Message;
import org.springframework.integration.support.MessageBuilder;

public class OrderTransformers {

    // Payload-only signature: framework unwraps the String, rewraps the Order.
    @Transformer(inputChannel = "orderJsonChannel", outputChannel = "orderChannel")
    public Order toOrder(String json) {
        return parse(json); // must NOT return null
    }

    // Full-Message signature: lets you also change headers.
    @Transformer(inputChannel = "orderChannel", outputChannel = "stampedChannel")
    public Message<Order> stamp(Message<Order> msg) {
        return MessageBuilder.fromMessage(msg)
                .setHeader("processedAt", System.currentTimeMillis())
                .build();
    }

    private Order parse(String json) { /* ... */ return new Order(); }
}

class Order {}

go deeper

for a junior

Know it changes the message (payload/headers) between two channels and always returns something.

for a middle

Know the two signatures (payload vs Message), the must-return rule, and built-ins like ObjectToJsonTransformer / header enricher.

for a senior

Contrast with filter/router; use MessageBuilder to alter headers; pick built-in vs custom; know MessageTransformingHandler.

for a principal

Reason about transformer placement vs. enrichers/content-enrichers, error semantics on the transformer channel, and keeping transformers side-effect-free for testability/replay.

## What a transformer is Spring Integration implements the Enterprise Integration Patterns. A **Message** is an envelope: a `payload` (the body) plus `headers` (a `MessageHeaders` map). Components are wired together with **MessageChannel**s (pipes). A **Transformer** is the EIP endpoint whose job is to take a message and produce a *changed* message — a different payload type, enriched content, or altered headers — with exactly **one input and one output**. It does not branch (that's a router) and does not drop messages (that's a filter). ## Declaring one with @Transformer ```java @Transformer(inputChannel = "orderJsonChannel", outputChannel = "orderChannel") public Order toOrder(String json) { ... } ``` The framework wraps the annotated method in a `MessageTransformingHandler`, subscribes it to `inputChannel`, and sends the return value to `outputChannel`. ### Method signatures accepted - `Message<?>` — you get the whole envelope (payload + headers). - Just the payload type (`String`, `Order`, `byte[]`…) — the framework **unwraps** the payload and passes it. This is the common form. - Return either a bare payload (framework rewraps it into a Message, copying headers) or a full `Message<?>` you build yourself (e.g. via `MessageBuilder`). ## The must-return rule (key gotcha) A transformer **must** return a non-null value. Returning `null` raises a `MessageTransformationException` / "transformer returned null" error — because by contract a transformer always yields a result. If you want the option to *drop* a message, that's a **filter**, not a transformer. ## Built-in transformers Spring Integration ships ready-made ones so you rarely hand-write serialization: - `ObjectToJsonTransformer` / `JsonToObjectTransformer` - `ObjectToStringTransformer`, `ObjectToMapTransformer`, `MapToObjectTransformer` - `PayloadTypeConvertingTransformer`, `PayloadDeserializingTransformer` - **Header enricher** (`<int:header-enricher>` / `.enrichHeaders(...)` in the DSL) — a transformer specialization that only adds/changes headers, leaving the payload intact. ## Java DSL equivalent ```java IntegrationFlow.from("orderJsonChannel") .transform(Transformers.fromJson(Order.class)) .channel("orderChannel") .get(); ``` ## When to use - Adapt between representations (JSON ↔ domain object, bytes ↔ String). - Enrich/normalize headers before routing. - Reshape a payload so the next endpoint sees the type it expects. ## Gotchas - Don't put branching logic here — use `@Router`. - Don't return `null` to skip — use `@Filter`. - Header changes on a returned bare payload: the framework copies existing headers; to *change* headers, return a `Message` built with `MessageBuilder.fromMessage(...).setHeader(...)`.

  • What happens if a @Transformer method returns null?
    It's an error — the transformer contract requires a result, so you get a MessageTransformationException/'transformer returned null'. To conditionally drop a message you use @Filter instead.
  • How would you change only headers without touching the payload?
    Use a header enricher (a transformer specialization) — <int:header-enricher> or .enrichHeaders(...) in the Java DSL — or return a Message built via MessageBuilder.fromMessage(msg).setHeader(...).build().

saying these in an interview costs you the question

  • Thinking a transformer can drop a message by returning null (that's a filter).
  • Confusing a transformer (reshape) with a router (branch) — a transformer never chooses a channel by content.
  • Believing you must always handle the raw Message — the framework unwraps the payload for you.

context

open as a page

How does @Router implement content-based routing in Spring Integration, and what is HeaderValueRouter?

level: middleimportance: must knowfreq 55%

basics

~20 s

@Router marks a method that inspects a message and returns the name(s) of the channel(s) to send it to — content-based routing. HeaderValueRouter is a built-in that picks the channel based on the value of a specified message header.

open as a page

Explain @Splitter and @Aggregator, and how correlation and release strategies tie a split flow back together.

level: seniorimportance: must knowfreq 50%

basics

~20 s

@Splitter breaks one message into many (e.g. a list into per-item messages). @Aggregator collects related messages back into one. They pair up: the aggregator uses a correlation strategy to group messages and a release strategy to decide when a group is complete and ready to emit.

open as a page

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

level: middleimportance: should knowfreq 45%

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).

open as a page

As a principal engineer, how would you design a reliable aggregator: correlation-key lifecycle, timeouts, persistence, and handling stragglers/duplicates?

level: principalimportance: should knowfreq 30%

basics

~20 s

Use a persistent MessageGroupStore so partial groups survive restarts, set a group-timeout so incomplete groups don't leak, decide via send-partial-result-on-expiry whether to emit or discard partial groups, and configure expire-groups-upon-completion plus idempotency to handle late/duplicate messages for a reused correlation key.

open as a page