How does @Router implement content-based routing in Spring Integration, and what is HeaderValueRouter?
answer
- answers 'where next?', never mutates
- return String name / MessageChannel / Collection (multi)
- HeaderValueRouter = route on one header value
- defaultOutputChannel + resolutionRequired = silent-drop control
- PayloadTypeRouter / RecipientListRouter / ExpressionEvaluatingRouter
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.
solid answer
~40 sA router is the EIP endpoint that decides *where* a message goes without changing it. With @Router(inputChannel="in"), your method returns a channel name (String), a MessageChannel, or a collection of either — for multi-target routing. The framework wraps it in a MessageRouter (AbstractMessageRouter). Key options: defaultOutputChannel for unmatched values, resolutionRequired=true to throw when no channel matches (vs. silently dropping), and a channelMapping so the method can return a logical key that maps to a real channel name. HeaderValueRouter is a ready-made router that routes on one header's value — e.g. route by header "type": order→orderChannel, refund→refundChannel — configured with a mapping and optional default. Routers never mutate the payload; they only select destinations. Unlike a filter (binary keep/drop), a router chooses among many outputs.
code
java · 22 linesimport org.springframework.integration.annotation.Router;
import org.springframework.messaging.Message;
public class TypeRouter {
// Returns a logical key; channelMapping (configured elsewhere) resolves the real channel.
@Router(inputChannel = "incoming", defaultOutputChannel = "deadLetterChannel",
resolutionRequired = "false")
public String route(Message<Order> msg) {
return (String) msg.getHeaders().get("type"); // e.g. "order" | "refund"
}
// Multi-channel: message is delivered to EVERY returned channel name.
@Router(inputChannel = "fanOut")
public java.util.List<String> broadcast(Order order) {
return order.isRush()
? java.util.List.of("fulfilmentChannel", "auditChannel")
: java.util.List.of("auditChannel");
}
}
class Order { boolean isRush() { return false; } String type() { return "order"; } }go deeper
Know a router picks the destination channel based on message content and doesn't change the message.
Know @Router return types, HeaderValueRouter/PayloadTypeRouter, defaultOutputChannel and resolutionRequired, and multi-channel routing.
Reason about channelMapping to decouple keys from wiring, silent-drop pitfalls, and choosing the right built-in router.
Design routing topologies (recipient lists, error-type routing), guarantee no silent loss via defaults/monitoring, and keep routers stateless and side-effect-free.
## Routers in one sentence A **Router** is the EIP component that performs **content-based routing**: it examines a message and forwards it to one (or several) of many possible **MessageChannel**s, *without altering* the message. It answers "where next?", not "what shape?" (transformer) or "keep or drop?" (filter). ## @Router basics ```java @Router(inputChannel = "incoming") public String route(Order order) { return order.isRush() ? "rushChannel" : "standardChannel"; } ``` The framework wraps the method in an `AbstractMessageRouter` (specifically a `MethodInvokingRouter`). The return value determines the target(s): - **`String`** — a channel *name* (resolved via the bean registry / `channelMapping`). - **`MessageChannel`** — a channel instance directly. - **`Collection<String>` / `String[]` / `Collection<MessageChannel>`** — **multi-channel** routing; the message is sent to *each* returned channel (recipient-list style). - **`null` / empty** — no target; behavior depends on the options below. ### Method input Like other endpoints, the method may take the full `Message<?>` or just the payload. ## Important router options - **`defaultOutputChannel`** — where messages go when the router resolves no matching channel. Without it, unresolved routing is an error or a drop depending on the next flag. - **`resolutionRequired`** (default effectively true for mapping-based routers) — if `true`, a message that matches no channel throws a `MessagingException` ("no channel resolved"); if `false`, it is silently dropped. This is the classic **silent-drop gotcha**: a typo'd header value + `resolutionRequired=false` + no default = messages vanish with no error. - **`channelMapping`** (a.k.a. `mapping`) — maps the *logical key* your method returns to an actual channel bean name, decoupling business keys from wiring. - **`ignoreSendFailures`**, `timeout` — send-side behavior. ## HeaderValueRouter (built-in, no custom code) Routes based on the **value of a single header**. XML: ```xml <int:header-value-router input-channel="in" header-name="type"> <int:mapping value="order" channel="orderChannel"/> <int:mapping value="refund" channel="refundChannel"/> </int:header-value-router> ``` Java DSL: ```java .<Order>route(Order::type, m -> m .channelMapping("order", "orderChannel") .channelMapping("refund", "refundChannel") .defaultOutputChannel("deadLetter")) ``` Related built-ins: **`PayloadTypeRouter`** (route by payload Java type), **`RecipientListRouter`** (send to a fixed list, optionally each gated by a selector expression), **`ExpressionEvaluatingRouter`** (SpEL expression yields the key), and error-channel routing via `ErrorMessageExceptionTypeRouter`. ## Router vs sibling patterns - **Filter** = 1 input, 0-or-1 output (keep/drop). **Router** = 1 input, 1-or-many outputs chosen by content. - A router **must not** modify the payload; if you need to reshape *and* branch, transform first, then route. ## Gotchas checklist - Missing `defaultOutputChannel` + `resolutionRequired=false` ⇒ **silent message loss**. Set a default or keep resolutionRequired true so failures surface. - Returning a channel *name* requires that bean to exist; prefer `channelMapping` to avoid leaking channel names into business code. - Routers are stateless — don't accumulate state there (that's an aggregator).
- A router returns a header value that matches no configured channel. How do you avoid silently losing the message?Set a defaultOutputChannel to catch unmatched values, or keep resolutionRequired=true so an unresolved route throws a MessagingException instead of dropping silently.
- What happens if the router method returns a Collection of channel names?The message is sent to every channel in the collection — multi-target/recipient-list-style routing. Returning a single String routes to one channel.
- Which built-in router would you use to branch on the payload's Java type?PayloadTypeRouter — it maps payload classes to channels. HeaderValueRouter branches on a header value; ExpressionEvaluatingRouter uses a SpEL expression.
saying these in an interview costs you the question
- Saying a router can transform/modify the payload — it must not; it only selects destinations.
- Assuming an unmatched route always errors — with resolutionRequired=false and no default it silently drops.
- Confusing router (choose among many) with filter (keep/drop one).