This is an automated email from the ASF dual-hosted git repository.

christophd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-website.git


The following commit(s) were added to refs/heads/main by this push:
     new 244bca39 chore: Add blog post: Camel elevates Citrus testing
244bca39 is described below

commit 244bca39c5f9eb9ec2d535a7dfadcceb02ea3394
Author: Christoph Deppisch <[email protected]>
AuthorDate: Tue Jul 7 10:56:07 2026 +0200

    chore: Add blog post: Camel elevates Citrus testing
---
 .../2026/07/camel-elevates-citrus/featured.png     | Bin 0 -> 911585 bytes
 .../blog/2026/07/camel-elevates-citrus/index.md    | 290 +++++++++++++++++++++
 2 files changed, 290 insertions(+)

diff --git a/content/blog/2026/07/camel-elevates-citrus/featured.png 
b/content/blog/2026/07/camel-elevates-citrus/featured.png
new file mode 100644
index 00000000..56c82f50
Binary files /dev/null and 
b/content/blog/2026/07/camel-elevates-citrus/featured.png differ
diff --git a/content/blog/2026/07/camel-elevates-citrus/index.md 
b/content/blog/2026/07/camel-elevates-citrus/index.md
new file mode 100644
index 00000000..d3d4fb5f
--- /dev/null
+++ b/content/blog/2026/07/camel-elevates-citrus/index.md
@@ -0,0 +1,290 @@
+---
+title: "How Apache Camel elevates Citrus Integration Testing to next levels"
+date: 2026-07-06
+draft: false
+authors: [christophd]
+categories: ["Tooling", "Howtos"]
+keywords: ["apache camel", "citrus", "integration testing", "data formats", 
"processors", "quarkus", "testcontainers"]
+preview: "Citrus gains the ability to speak every protocol that Camel 
supports, transform test data with Camel's rich data formats, and reach into 
running Camel routes to trigger or verify business logic directly. This post 
explores five concrete ways Apache Camel elevates Citrus integration testing."
+---
+
+ [Citrus](https://citrusframework.org) is a powerful integration testing 
framework that orchestrates end-to-end test scenarios with clean, readable Java 
code. As a framework Citrus provides several ready-to-use components to connect 
to messaging systems, cloud services, and protocols such as Http, Kafka, Jms 
and many more — but when you combine Citrus with the power of Apache Camel, 
something remarkable happens.
+
+Citrus gains the ability to speak every protocol Camel supports, transform 
test data with Camel's rich data formats, and even reach into running Camel 
routes to trigger or verify business logic directly. In return, Camel gets a 
purpose-built test harness that can validate its routes in realistic, 
container-backed environments with proper message verification.
+
+This post explores five concrete ways Apache Camel elevates Citrus integration 
testing. Each section focuses on a real scenario, backed by working sample 
projects that you can clone and run. By the end you will have a clear picture 
of how the two frameworks complement each other and where to reach for Camel 
when writing Citrus tests.
+
+# Using Camel components to send and receive messages
+
+The most immediate benefit of Camel integration in Citrus is access to Camel's 
massive component library. Citrus has excellent built-in support for protocols 
like HTTP, Kafka, JMS, and FTP — but the world of integration is vast. What 
about MQTT? AMQP 1.0? gRPC? Rather than waiting for Citrus to add native 
support for every protocol, you can leverage any Camel component directly in 
your tests.
+
+The key is the `CamelSupport.camel()` DSL, which lets you send and receive 
messages through any Camel endpoint URI:
+
+```java
+runner.when(
+    camel()
+        .send()
+        .endpoint("paho-mqtt5:temperature?brokerUrl={{mqtt.broker.url}}")
+        .message()
+        .fork(true)
+        .body("{\"value\": 25}")
+);
+```
+
+This snippet sends an MQTT message using Camel's `paho-mqtt5` component — 
something Citrus cannot do on its own. The message is published to the 
`temperature` topic on an MQTT broker, and the `{{mqtt.broker.url}}` 
placeholder is resolved from the application's Camel context at runtime.
+
+What makes this powerful is how natural it feels. The `camel().send()` action 
follows the same Given-When-Then pattern as any other Citrus test action. You 
specify the Camel endpoint URI, set the message body and headers, and let 
Citrus handle the rest.
+
+This approach can be mixed with arbitrary Citrus send and receive operations 
on endpoints such as Kafka. In the sample application the Camel route consumes 
MQTT messages, extracts the temperature value from the JSON body with a JQ 
expression, and uses a Content Based Router to decide whether the reading is 
warm or cold. Readings above 20 degrees go to the `temperature-warm` Kafka 
topic, everything else to `temperature-cold`. The test verifies this routing 
decision by sending a warm readi [...]
+
+```java
+@CitrusEndpoint
+@KafkaEndpointConfig(topic = "temperature-warm")
+KafkaEndpoint temperatureWarm;
+
+runner.then(
+    receive()
+        .endpoint(temperatureWarm)
+        .message()
+        .body("25")
+);
+```
+
+The test sends MQTT and verifies Kafka — two different protocols, coordinated 
in a single test method. Citrus handles the MQTT side through Camel and the 
Kafka side through its own native endpoint. This multi-protocol testing is 
where the combination really shines.
+
+The `.fork(true)` flag deserves a brief mention. It tells Citrus to send the 
message asynchronously so the test thread can proceed to the verification step. 
Without it, the test would block waiting for the MQTT publish to complete 
before checking Kafka, which could cause timing issues with asynchronous routes.
+
+You can see this pattern in action in the [Camel MQTT 
sample](https://github.com/citrusframework/citrus-quarkus-examples/tree/main/apache-camel/camel-mqtt).
+
+# Connecting with third-party services Citrus does not support out of the box
+
+The MQTT example above illustrates protocol bridging, but Camel's component 
library goes far beyond messaging protocols. It covers cloud services like AWS 
S3, Azure Blob Storage, and Google Cloud Pub/Sub. It connects to databases, 
file systems, social media APIs, and dozens more. Any of these become available 
to your Citrus tests the moment you add the corresponding Camel dependency.
+
+Consider a Camel route that polls an AWS S3 bucket, splits each file into 
individual lines, wraps each line as a JSON event, and publishes the result to 
Kafka. Testing this route requires uploading a file to S3 — but Citrus does not 
have a native S3 endpoint. With Camel, you can upload the file using the 
`aws2-s3` component:
+
+```java
+runner.when(
+    camel()
+        .send()
+        .endpoint("aws2-s3://citrus-camel-demo?" +
+                      "overrideEndpoint=true&" +
+                      "forcePathStyle=true&" +
+                      "uriEndpointOverride={{aws.s3.uriEndpointOverride}}&" +
+                      "accessKey={{aws.s3.accessKey}}&" +
+                      "secretKey={{aws.s3.secretKey}}&" +
+                      "region={{aws.s3.region}}")
+        .message()
+        .fork(true)
+        .body("Hello Camel!\nHello Citrus!\nHello Quarkus!")
+        .header("CamelAwsS3Key", "hello.txt")
+);
+```
+
+The test writes a multi-line text file to an S3 bucket backed by a local AWS 
test instance that Citrus provisions automatically via the 
`@LocalStackContainerSupport` annotation and Testcontainers. The Camel endpoint 
URI points to the same AWS test instance that the application route polls, so 
the uploaded file is picked up and processed just as it would be in production.
+
+After the route processes the file, the test verifies each line appears as a 
separate JSON event on Kafka:
+
+```java
+runner.then(
+    receive()
+        .endpoint(s3Events)
+        .message()
+        .body("{ \"message\": \"Hello Camel!\" }")
+);
+```
+
+The pattern is the same as before: use Camel to interact with a technology 
that Citrus does not support natively, then verify the outcome with a Citrus 
endpoint that does. The infrastructure — a local AWS test instance for S3, a 
Kafka broker — is managed by Citrus annotations and Testcontainers, keeping the 
test self-contained and reproducible.
+
+This approach scales to any service Camel supports. Need to drop a message on 
a RabbitMQ queue? Use `camel().send().endpoint("rabbitmq:...")`. Need to write 
a file to an FTP server? Use `camel().send().endpoint("ftp:...")`. The Camel 
component catalog becomes your test toolkit.
+
+The full example is available in the [Camel AWS S3 to Kafka 
sample](https://github.com/citrusframework/citrus-quarkus-examples/tree/main/apache-camel/camel-aws-s3-kafka).
+
+# Transforming test data with Camel data formats
+
+Real-world integration rarely deals with plain strings. Messages arrive as 
compressed archives, Base64-encoded payloads, serialized Protobuf messages, or 
structured CSV files. When your Camel route expects a ZIP file as input, your 
test needs to produce a ZIP file. You could write the compression logic by hand 
— create a `ZipOutputStream`, add entries, close the stream — but that is 
boilerplate that obscures the test's intent.
+
+Citrus lets you tap into Camel's data format system to marshal and unmarshal 
test data declaratively. Here is how you create a ZIP file in a test using 
Camel's `zipFile` data format:
+
+```java
+runner.when(
+    camel()
+        .send()
+        .endpoint("file:inbox")
+        .message()
+        .header("CamelFileName", "words.zip")
+        .body("Hello World")
+        .transform(processor().camel()
+                .marshal()
+                .zipFile())
+);
+```
+
+The `.transform()` call is where Camel's data format integration kicks in. The 
`processor().camel().marshal().zipFile()` expression tells Citrus to pass the 
message body through Camel's ZIP file marshaller before writing it to disk. The 
result is a properly compressed `words.zip` file in the `inbox` directory — 
exactly what the Camel route expects to consume.
+
+Compare this with the manual approach:
+
+```java
+// Manual ZIP creation — verbose and error-prone
+ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ZipOutputStream zos = new ZipOutputStream(baos);
+zos.putNextEntry(new ZipEntry("content.txt"));
+zos.write("Hello World".getBytes());
+zos.closeEntry();
+zos.close();
+byte[] zipContent = baos.toByteArray();
+```
+
+The Camel-powered version is one line of transformation logic. It is 
declarative, readable, and uses the exact same data format implementation that 
the production route uses for unmarshalling. This test-production parity 
matters — if the route changes its compression format, the test adapts by 
changing one data format call.
+
+The same pattern works with any Camel data format. Need to create JSON test 
data?
+
+```java
+.transform(processor().camel().marshal().json())
+```
+
+Base64-encoded content?
+
+```java
+.transform(processor().camel().marshal().base64())
+```
+
+GZIP compression?
+
+```java
+.transform(processor().camel().marshal().gzip())
+```
+
+Camel supports dozens of data formats including CSV, Protobuf, Avro, JAXB, and 
many more. All of them are available for creating test data through the same 
`.transform(processor().camel().marshal()...)` pattern.
+
+The complete example is in the [Camel File Inbox 
sample](https://github.com/citrusframework/citrus-quarkus-examples/tree/main/apache-camel/camel-file-inbox).
+
+# Using Camel processors for verification
+
+Data formats handle structured transformations like compression and 
serialization, but sometimes you need more general processing power during 
verification. Camel processors fill this gap. They give you access to Camel's 
type conversion system, header manipulation, and arbitrary processing logic — 
all usable within Citrus test actions.
+
+A common scenario is verifying the content of files produced by a Camel route. 
When a route aggregates multiple messages and writes the result to a file, the 
test needs to read that file and validate its content. The file arrives as a 
byte stream, but you want to validate it as a string. Camel's `convertBodyTo` 
processor handles the conversion:
+
+```java
+runner.then(
+    camel()
+        .receive()
+        .endpoint("file:outbox")
+        .process(processor().camel().convertBodyTo(String.class))
+        .message()
+        .header("CamelFileName", "tasks.txt")
+        .body("""
+        Doctor's appointment 9:00am
+        Fetch kids from school
+        Plan next vacation in June
+        """)
+);
+```
+
+The `.process()` call applies a Camel processor to the received message before 
validation. In this case, `convertBodyTo(String.class)` uses Camel's type 
conversion system to turn the raw file bytes into a `String`, which Citrus can 
then compare against the expected body content.
+
+This approach works because Citrus and the application share the same 
`CamelContext`. The test injects and registers it:
+
+```java
+@Inject
+@BindToRegistry
+CamelContext camelContext;
+```
+
+With the shared context, the test has access to all of Camel's processing 
capabilities: type converters, data formats, header manipulation, and even 
custom processor logic. Instead of writing manual file-reading code, you 
express the transformation declaratively and let Camel do the heavy lifting.
+
+The processor integration is not limited to type conversion. You can chain 
multiple processors, apply data format unmarshalling on received messages, or 
use any custom Camel processor that your application defines. This keeps test 
code focused on what it is verifying rather than how to prepare data for 
verification.
+
+See the full example in the [Camel File Outbox 
sample](https://github.com/citrusframework/citrus-quarkus-examples/tree/main/apache-camel/camel-file-outbox).
+
+# Accessing internal Camel routes for test control
+
+The examples so far use Camel as a tool within Citrus — sending messages 
through Camel components, transforming data with Camel formats and processors. 
But there is another dimension to the integration: reaching into the 
application's own Camel routes to trigger business logic or inject test data at 
precise points in the processing pipeline.
+
+Camel's `direct:` component provides synchronous, in-memory endpoints that are 
ideal for this. When your application defines a route starting with 
`from("direct:tasks")`, a Citrus test can send messages directly to that 
endpoint without going through any external transport:
+
+```java
+runner.when(
+    send()
+        .endpoint("camel:direct:tasks")
+        .message()
+        .body("Doctor's appointment 9:00am")
+);
+```
+
+The `camel:direct:tasks` URI is a Citrus convention that resolves to the Camel 
`direct:tasks` endpoint in the shared CamelContext. This gives the test direct 
access to the route's entry point — no message broker, no HTTP server, no 
external transport required. Messages are passed in-memory, making this the 
fastest way to trigger a Camel route from a test.
+
+This pattern is particularly useful for testing complex Camel routes that 
involve multiple processing steps. Consider a route that receives messages 
through a direct endpoint, aggregates three of them, and writes the result to a 
file. The test can send exactly three messages to trigger the aggregation:
+
+```java
+runner.when(
+    Arrays.asList(
+        send().endpoint("camel:direct:tasks").message()
+            .body("Doctor's appointment 9:00am"),
+        send().endpoint("camel:direct:tasks").message()
+            .body("Fetch kids from school"),
+        send().endpoint("camel:direct:tasks").message()
+            .body("Plan next vacation in June")
+    )
+);
+```
+
+All three messages are delivered in-memory to the same Camel route, triggering 
the aggregation logic and producing the output file. The test has full control 
over what enters the route and can verify the aggregated result on the other 
side.
+
+Direct endpoint access also works well with Camel's `mock:` component for 
verification. When a route ends with `to("mock:words-out")`, the test can 
access that mock endpoint through the shared CamelContext and assert on the 
messages it received:
+
+```java
+MockEndpoint mockEndpoint = getMockEndpoint("mock:words-out");
+mockEndpoint.expectedBodiesReceived(">> HOWDY");
+
+runner.when(
+    send()
+        .fork(true)
+        .endpoint("camel:direct:words-in")
+        .message()
+        .header("lang", "us-texas")
+        .body("Hello")
+);
+
+runner.then(
+    context -> {
+        try {
+            mockEndpoint.assertIsSatisfied();
+        } catch (InterruptedException e) {
+            throw new CitrusRuntimeException("Failed to verify mock endpoint", 
e);
+        }
+    }
+);
+```
+
+This combination of direct endpoints for input and mock endpoints for output 
gives you surgical precision in testing. You bypass external transports 
entirely, focus on the route's transformation and routing logic, and verify the 
results through Camel's own mock assertion API.
+
+The `.fork(true)` flag is important here because the `direct:` component is 
synchronous — it executes the route in the caller's thread. Without forking, 
the send action would block until the entire route completes, which can cause 
timing issues when the route makes external calls (like the HTTP translation 
service in the example above) that the test needs to intercept.
+
+Explore this pattern in the [Camel Direct 
sample](https://github.com/citrusframework/citrus-quarkus-examples/tree/main/apache-camel/camel-direct)
 for simple route testing and the [Camel Direct HTTP 
sample](https://github.com/citrusframework/citrus-quarkus-examples/tree/main/apache-camel/camel-direct-http)
 for a more advanced scenario that combines direct endpoint access with HTTP 
service simulation.
+
+# Getting started
+
+All the capabilities described in this post are available through the 
`citrus-camel` module. Add it as a test-scoped dependency in your Maven POM:
+
+```xml
+<dependency>
+    <groupId>org.citrusframework</groupId>
+    <artifactId>citrus-camel</artifactId>
+    <version>${citrus.version}</version>
+    <scope>test</scope>
+</dependency>
+```
+
+The module provides the `CamelSupport.camel()` DSL for sending and receiving 
through Camel endpoints, the `processor().camel()` API for data formats and 
processors, and the `@BindToRegistry` annotation for sharing the CamelContext 
between your application and tests.
+
+The sample projects referenced throughout this post are available in the 
[citrus-quarkus-examples](https://github.com/citrusframework/citrus-quarkus-examples/tree/main/apache-camel)
 repository. Each is a self-contained Quarkus application with a Camel route 
and a Citrus test that verifies it. Clone the repository, pick a sample, and 
run `./mvnw clean test` to see it in action.
+
+# Wrapping up
+
+Apache Camel and Citrus are a natural fit. Camel's strength is connecting 
systems — its 300+ components, rich data formats, and enterprise integration 
patterns cover virtually every integration scenario. Citrus's strength is 
testing those integrations — orchestrating multi-protocol test flows, 
provisioning infrastructure, and validating message content with precision.
+
+When you bring them together, Citrus tests gain the ability to speak any 
protocol Camel supports, transform test data with production-grade data 
formats, and reach directly into Camel routes for surgical testing. The five 
patterns we explored — using Camel components for protocol bridging, connecting 
with unsupported services, transforming test data with data formats, verifying 
with processors, and accessing internal routes — give you a comprehensive 
toolkit for testing even the most com [...]
+
+The best part is how little ceremony is involved. A shared CamelContext, a few 
imports, and the familiar Citrus Given-When-Then pattern. The code reads like 
what it does, and the tests run fast because infrastructure is managed by 
Citrus and Testcontainers.
+
+Give it a try, and let us know what you think!

Reply via email to