25:00
Focus
Lesson 6

Common Enterprise Integration Patterns

~13 min125 XP

Introduction

Enterprise systems rarely speak the same language — messages fly between services in different formats, volumes, and destinations. Apache Camel gives you a battle-tested toolkit of Enterprise Integration Patterns (EIPs) that solve these routing and transformation challenges with elegant, reusable building blocks. In this lesson, you'll master four of the most powerful patterns: the Content-Based Router, Filter, Splitter, and Aggregator.

What Are Enterprise Integration Patterns?

Back in 2003, Gregor Hohpe and Bobby Woolf published Enterprise Integration Patterns, a catalog of 65 solutions to recurring messaging problems. Think of them like design patterns (à la Gang of Four), but specifically for the world of message-driven systems. Apache Camel implements virtually all of them out of the box.

Every EIP in Camel fits into a route — a pipeline that describes how a message travels from a source (consumer) to a destination (producer). Along the way, EIPs act as processing stations that inspect, split, filter, or transform the message.

The mental model to keep in mind: imagine a postal sorting facility. Letters (messages) come in on a conveyor belt, workers (EIPs) read the labels, redirect them to the right chutes, tear open bulk packages into individual envelopes, discard junk mail, and bundle related letters into one bag. Each of those workers is an EIP.

Camel routes are written in its Domain-Specific Language (DSL), available in Java, XML (Spring/Blueprint), and YAML. This lesson uses the Java DSL since it's the most expressive for learning.

The four patterns we'll cover map naturally to those postal workers:

  • Content-Based Router → reads the label and picks the right chute
  • Filter → discards unwanted mail
  • Splitter → tears open a bulk package into individual pieces
  • Aggregator → bundles related pieces back together
Exercise 1Multiple Choice
What is the primary purpose of Enterprise Integration Patterns (EIPs) in Apache Camel?

Content-Based Router: Directing Traffic by Message Content

The Content-Based Router (CBR) is arguably the most commonly used EIP. Its job is simple: look at a message's content (headers, body, properties) and route it to one of several possible endpoints based on what it finds. This is exactly like a switch/case statement, but for message flows.

In Camel's Java DSL, you build a CBR using .choice(), followed by .when() clauses, and an optional .otherwise() fallback.

The .when() clause accepts a Predicate — any expression that evaluates to true or false. Camel ships with a rich expression language called Simple, as well as support for XPath, JsonPath, SpEL, and more.

Common pitfall: Forgetting .end() at the end of a .choice() block. Without it, subsequent steps in the route are ambiguous — Camel won't know if they belong inside the choice or after it. Always close your choice() with .end().

Another pitfall: using .choice() when you actually want different threads processing concurrently. CBR is a purely sequential decision — only one when() branch fires per message.

A powerful technique is combining CBR with header enrichment — setting metadata on the message before the router so that downstream systems know what decision was made, without having to re-inspect the body.

Filter: Silencing the Noise

The Filter pattern is simpler than CBR: it has exactly one condition. If the condition is true, the message continues; if false, the message is dropped entirely — it doesn't go anywhere, not even to a fallback. This is the key distinction from CBR's otherwise().

Use Filter when you want to act on a subset of messages flowing through a route without changing the route's main logic. It's a gatekeeper, not a router.

In Camel's Java DSL, you use .filter():

from("activemq:topic:sensor-readings")
    .filter(simple("${body.temperature} > 100"))
    .to("activemq:queue:overheating-alerts");

Messages with temperature ≤ 100 are silently discarded. Only alerts that matter reach the overheating queue.

Why not just use choice().when().otherwise()? You could, but Filter is more declarative — it communicates intent clearly. When a reader sees .filter(), they immediately know "some messages are dropped here." With choice(), they need to check whether there's an otherwise() branch.

Note: A filtered-out message is not an error. Camel does not throw an exception or log a warning — it simply stops processing that exchange. If you need to know what was dropped (for auditing), add a .log() or .to() inside a wrapping choice().when(predicate).otherwise().stop() pattern instead.

Common pitfall: Expecting filtered messages to continue to the next .from() or route. They don't. A filtered message is gone. If you need to redirect rejects, use CBR.

Exercise 2True or False
In Apache Camel, when a message does not match the Filter predicate, it is automatically sent to a dead-letter queue.

Splitter: Divide and Conquer

The Splitter pattern solves a very common problem: you receive one message that contains many items (a batch order with 50 line items, a CSV file with 1,000 rows, a JSON array of events), and you need to process each item individually.

Camel's .split() breaks a single Exchange into multiple sub-exchanges, each carrying one item. These sub-exchanges travel the rest of the route independently.

You provide Camel an Expression that tells it how to split the body. For a Java List, it can split automatically. For XML, you'd use an XPath expression. For JSON arrays, JsonPath works perfectly.

By default, splitting is sequential — each sub-exchange is processed one after another. For high-throughput scenarios, you can add .parallelProcessing() to process sub-exchanges concurrently.

The aggregation callback: By default, after all splits are processed, Camel can optionally re-aggregate the results using a AggregationStrategy. If you pass one into .split(), the split results are collected back into a single exchange. If you don't, the sub-exchanges are fire-and-forget.

Common pitfall: Streaming large files. If you split a 2GB XML file by loading it entirely into memory first, you'll get an OutOfMemoryError. Use Camel's Streaming mode with .split().streaming() to process elements one at a time without loading the full document.

Exercise 3Multiple Choice
You are splitting a 500MB XML file into individual <record> elements using Apache Camel's Splitter. What option should you add to avoid an OutOfMemoryError?

Aggregator: Bringing It All Back Together

The Aggregator is the inverse of the Splitter — and the most complex of our four patterns. It collects multiple individual messages and combines them into a single, consolidated message. Think of it as reducing a stream of events down to a meaningful summary.

The Aggregator needs answers to three questions:

  1. Which messages belong together? → defined by a Correlation Expression
  2. How should they be combined? → defined by an AggregationStrategy
  3. When is the group complete? → defined by a Completion Condition

The correlation expression is the grouping key. For example, header("orderId") groups all messages that share the same order ID. Messages with different order IDs form separate groups simultaneously.

The AggregationStrategy is a Java interface with one method: aggregate(Exchange oldExchange, Exchange newExchange). You implement it to define how to merge a new incoming message into the accumulated result.

The completion condition tells Camel when to release the aggregated message downstream. You have several options:

  • .completionSize(N) — release when N messages have been collected
  • .completionTimeout(ms) — release after a period of inactivity
  • .completionPredicate(predicate) — release when a condition is met (e.g., a "last" flag in a header)
  • Combinations of the above

Note: Camel's Aggregator stores in-progress groups in an AggregationRepository. By default this is in-memory, which means if the application crashes, partially aggregated groups are lost. For production systems, use a persistent repository backed by a database or Hazelcast.

Exercise 4Multiple Choice
In Apache Camel's Aggregator, what is the role of the 'Correlation Expression'?

Combining Patterns: Building Real Pipelines

In practice, you rarely use just one EIP at a time. The real power of Apache Camel emerges when you chain these patterns together to build sophisticated integration pipelines. A single route might route by content, filter noise, split a batch, and then aggregate results — each pattern handling its specific responsibility cleanly.

Consider a real-world scenario: a retail system receives bulk order files from multiple partners. Here's how the four patterns compose naturally:

  1. CBR inspects the partner header and routes XML files to an XML processor and CSV files to a CSV processor
  2. Filter discards test/sandbox orders (those with orderId starting with "TEST-")
  3. Splitter breaks the batch file into individual order records
  4. Aggregator collects all processed orders from the same batch and emits a single "batch processed" confirmation event

This is the Pipes and Filters architectural style — each stage has a single responsibility and hands off to the next. This makes routes easy to test, reason about, and extend.

Testing tip: Camel provides a MockEndpoint (mock: URI scheme) that lets you assert how many messages arrived, in what order, and with what content. Combined with CamelTestSupport (JUnit), you can unit test each pattern in isolation without needing a real message broker.

Performance tip: When combining Splitter with Aggregator (a classic split-process-aggregate pipeline), make sure your Aggregation Repository can handle the concurrency level implied by your parallel split. An in-memory repository is fine for dev; in production, use a clustered store.

Note: Camel 3.x and 4.x have moved toward annotation-based and YAML-based route configuration. The Java DSL remains fully supported and is the best way to understand how patterns compose — learn it first, then adopt YAML DSL for declarative configuration management.

Key Takeaways

  • The Content-Based Router uses .choice().when().otherwise().end() to direct messages to different endpoints based on content — always close it with .end()
  • The Filter silently drops messages that don't match its predicate; unlike CBR, there is no fallback — dropped messages are gone
  • The Splitter breaks one message into many sub-exchanges for individual processing; use .streaming() for large files to avoid memory issues
  • The Aggregator collects multiple messages into one using a Correlation Expression (grouping), AggregationStrategy (merging), and a Completion Condition (release trigger)
  • These four patterns compose naturally into pipelines — real integration routes typically chain several EIPs together, each with a single clear responsibility
  • For production Aggregators, always use a persistent AggregationRepository to survive application restarts
Check Your Understanding

Apache Camel's Content-Based Router and Filter patterns are both used to control which messages proceed through a route, but they work in fundamentally different ways. Compare and contrast the Content-Based Router and Filter patterns: explain what each pattern does, what problem it solves, and describe a real-world scenario where you would choose one over the other. Use the postal sorting facility analogy from the lesson if it helps clarify your reasoning.

🔒Upgrade to submit written responses and get AI feedback
Go deeper
  • What year were Enterprise Integration Patterns published?🔒
  • How does Camel's YAML DSL differ from Java DSL?🔒
  • Can multiple EIPs be chained together in one route?🔒
  • What happens when a message matches no Content-Based Router condition?🔒
  • How does the Aggregator know when to stop collecting messages?🔒