Contents
- Content-Based Router — choice/when/otherwise
- Predicates in when()
- Message Filter — filter()
- Recipient List
- Dynamic Router
- Combining Patterns
Use .choice() → .when(predicate) → .otherwise() → .end() to route messages to different endpoints based on header values, body content, or any predicate. Only the first matching when branch executes.
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
@Component
public class ContentBasedRouter extends RouteBuilder {
@Override
public void configure() {
from("direct:orders")
.choice()
.when(header("orderType").isEqualTo("EXPRESS"))
.log("Express order → fast lane")
.to("direct:expressProcessing")
.when(header("orderType").isEqualTo("STANDARD"))
.log("Standard order → normal lane")
.to("direct:standardProcessing")
.when(header("amount").isGreaterThan(1000))
.log("High-value order → review queue")
.to("direct:reviewQueue")
.otherwise()
.log("Unknown order type — sending to DLQ")
.to("direct:deadLetter")
.end();
}
}
Always include .otherwise() to handle messages that match no when clause. Without it, unmatched messages pass through the choice block silently with no processing — which is rarely what you want.
Camel provides a rich set of built-in predicates for use inside .when():
| Predicate | Example |
| Header equality | header("type").isEqualTo("A") |
| Header contains | header("name").contains("urgent") |
| Header regex | header("sku").regex("^[A-Z]{3}\\d{4}$") |
| Header numeric comparison | header("qty").isGreaterThan(100) |
| Header is null / not null | header("correlationId").isNull() |
| Body contains string | body().contains("ERROR") |
| Simple expression | simple("${header.priority} == 'HIGH'") |
| XPath (for XML bodies) | xpath("/order/status = 'APPROVED'") |
| JSONPath (for JSON bodies) | jsonpath("$.status == 'APPROVED'") |
| Compound (and/or) | header("a").isEqualTo("X").and(header("b").isGreaterThan(0)) |
from("direct:xml-orders")
.choice()
.when(xpath("/order/priority = 'URGENT'"))
.to("direct:urgent")
.when(jsonpath("$.status == 'PENDING'"))
.to("direct:pending")
.when(simple("${header.amount} > 500 && ${header.currency} == 'USD'"))
.to("direct:highValueUsd")
.otherwise()
.to("direct:default")
.end();
.filter(predicate) drops (stops processing of) any message that does not satisfy the predicate. Messages that match continue through the route; non-matching messages are discarded silently unless you configure a filterRejected endpoint.
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
@Component
public class MessageFilterRoute extends RouteBuilder {
@Override
public void configure() {
// Only process events with severity ERROR or CRITICAL
from("direct:events")
.filter(header("severity").in("ERROR", "CRITICAL"))
.log("Critical event: ${body}")
.to("direct:alerting");
// Filter using a Simple expression
from("direct:transactions")
.filter(simple("${header.amount} > 1000"))
.to("direct:largeTransactions");
// Filter using XPath on an XML body
from("direct:xmlFeed")
.filter(xpath("/item/category = 'tech'"))
.to("direct:techNews");
}
}
Unlike .choice(), .filter() does not have an "else" branch. If you need to route non-matching messages elsewhere, use .choice().when(predicate)...otherwise()... instead.
The Recipient List EIP sends a copy of the message to a dynamic list of endpoints determined at runtime. The list can come from a header, a body field, or a custom bean.
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
@Component
public class RecipientListRoute extends RouteBuilder {
@Override
public void configure() {
// Header contains comma-separated endpoints: "direct:a,direct:b,seda:c"
from("direct:broadcast")
.recipientList(header("destinations"))
.parallelProcessing() // send to all endpoints concurrently
.end();
// Compute destinations dynamically via a bean
from("direct:dynamicBroadcast")
.recipientList(method(DestinationResolver.class, "resolve"))
.ignoreInvalidEndpoints()
.end();
}
}
import org.apache.camel.Header;
import org.springframework.stereotype.Component;
@Component
public class DestinationResolver {
public String[] resolve(@Header("orderRegion") String region) {
return switch (region) {
case "EU" -> new String[]{"direct:euWarehouse", "direct:euAudit"};
case "APAC" -> new String[]{"direct:apacWarehouse"};
default -> new String[]{"direct:defaultWarehouse"};
};
}
}
The Dynamic Router calls a bean or expression repeatedly to decide the next endpoint until it returns null (which signals the end of routing). Unlike Recipient List, it routes the same exchange sequentially through multiple endpoints.
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
@Component
public class DynamicRouterRoute extends RouteBuilder {
@Override
public void configure() {
from("direct:dynamic")
.dynamicRouter(method(StepRouter.class, "route"));
}
}
import org.apache.camel.Exchange;
import org.springframework.stereotype.Component;
@Component
public class StepRouter {
public String route(Exchange exchange) {
int step = exchange.getProperty("step", 0, Integer.class);
exchange.setProperty("step", step + 1);
return switch (step) {
case 0 -> "direct:validate";
case 1 -> "direct:enrich";
case 2 -> "direct:persist";
default -> null; // null ends the dynamic routing
};
}
}
Patterns compose naturally in the Java DSL. A common pattern is to filter first, then route by content, then use a Recipient List for fan-out:
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
@Component
public class CombinedRoute extends RouteBuilder {
@Override
public void configure() {
from("direct:incoming")
// 1. Drop any test messages
.filter(header("env").isNotEqualTo("TEST"))
// 2. Route by order type
.choice()
.when(header("type").isEqualTo("B2B"))
.to("direct:b2bPipeline")
.when(header("type").isEqualTo("B2C"))
.to("direct:b2cPipeline")
.otherwise()
.to("direct:unknownTypeDlq")
.end();
// 3. Inside b2bPipeline: fan-out to multiple systems
from("direct:b2bPipeline")
.setHeader("destinations", constant("direct:erp,direct:crm,direct:audit"))
.recipientList(header("destinations"))
.parallelProcessing()
.end();
}
}
Tips for maintainable routing logic:
- Keep each when() branch short — delegate to a direct: sub-route for complex logic.
- Name every route with .routeId("...") to make logs and JMX metrics readable.
- Prefer header-based routing over body inspection when possible — parsing the body inside a predicate is expensive at scale.
- Use .otherwise().to("direct:dlq") rather than swallowing unmatched messages silently.