What does @Transformer do in Spring Integration, and how does a transformer endpoint fit into a message flow?
answer
- one in, one out, always a result
- MessageTransformingHandler wraps the method
- null = error (that's a filter's job)
- payload-unwrap vs full Message signature
- 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 sA 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 linesimport 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
Know it changes the message (payload/headers) between two channels and always returns something.
Know the two signatures (payload vs Message), the must-return rule, and built-ins like ObjectToJsonTransformer / header enricher.
Contrast with filter/router; use MessageBuilder to alter headers; pick built-in vs custom; know MessageTransformingHandler.
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.