Extremely Serious

Category: Apache Camel

Apache Camel SEDA

Apache Camel SEDA implements the Staged Event-Driven Architecture pattern, enabling in-VM asynchronous messaging that decouples producers from consumers via BlockingQueue. This excels in high-load scenarios where synchronous endpoints like Direct would block threads—SEDA queues messages instead, boosting scalability with configurable concurrent consumers.

Core Advantages

  • Non-blocking Producers: Senders complete instantly while slow consumers process from the queue, preventing cascade failures.
  • Thread Pool Efficiency: Multiple consumers (concurrentConsumers=3) parallelize work without manual thread management.
  • Configurable Resilience: Options like queueSize, discardWhenFull, and offerTimeout handle overload gracefully.

Example

This standalone app uses Camel Main (Camel 4.x) with a custom ExchangeFormatter to visualize thread names, exchange IDs, and route context—clearly demonstrating SEDA's parallel consumer threads. Producers fire 5 messages rapidly (100ms intervals) into SEDA, while consumers lag with 1s delays; logs reveal immediate sends followed by staggered, multi-threaded processing.

import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.spi.ExchangeFormatter;

/**
 * Apache Camel example demonstrating SEDA (Staged Event-Driven Architecture) pattern.
 * Shows asynchronous message processing with concurrent consumers and queuing.
 */
public class SedaExample {

    static void main(String[] args) throws Exception {
        // Create a new Camel Main instance (using fully qualified name to avoid conflict)
        org.apache.camel.main.Main main = new org.apache.camel.main.Main();

        // Add routes with SEDA processing
        main.configure().addRoutesBuilder(new RouteBuilder() {
            @Override
            public void configure() {
                // Create a custom ExchangeFormatter for detailed log output
                ExchangeFormatter customFormatter = exchange ->
                        String.format("[Thread: %s] Body: %s | ExchangeId: %s | RouteId: %s",
                                Thread.currentThread().getName(),
                                exchange.getIn().getBody(String.class),
                                exchange.getExchangeId(),
                                exchange.getFromRouteId());

                // Register the custom formatter in the Camel registry
                getContext().getRegistry().bind("customFormatter", customFormatter);

                // SEDA endpoint: queueId=myQueue, concurrent consumers for parallelism
                from("seda:myQueue?concurrentConsumers=3")
                    .log("Processing: ${body}")
                    .delay(1000)  // Simulate slow consumer (1s delay)
                    .to("log:output?showAll=true&exchangeFormatter=#customFormatter");

                // Producer route for demo
                from("timer:tick?repeatCount=5&delay=100")  // Fire 5 msgs quickly
                    .setBody().simple("Msg ${exchangeId}")
                    .log("Sending: ${body}")
                    .to("seda:myQueue");
            }
        });

        // Run Camel (will run until Ctrl+C is pressed)
        main.run(args);
    }
}

Running and Verification

Compile and run the Java class directly (requires Camel 4.14.0 on classpath). Sample output shows the advantage:

Sending: Msg 1CB44DE50955685-0000000000000000     // Producer thread - instant
Sending: Msg 1CB44DE50955685-0000000000000001     // Producer continues rapidly
output - [Thread: Camel (camel-1) thread #6 - Delay] Body: Msg 1CB44DE50955685-0000000000000000 | ExchangeId: 1CB44DE50955685-0000000000000002 | RouteId: route1
output - [Thread: Camel (camel-1) thread #7 - Delay] Body: Msg 1CB44DE50955685-0000000000000001 | ExchangeId: 1CB44DE50955685-0000000000000004 | RouteId: route1  // Parallel consumers

Contrast with direct:myQueue: sends block behind delays on single thread. SEDA's queue absorbs bursts across threads, perfect for enterprise workloads like order processing.

Apache Camel Bean EIP

Apache Camel's Bean Enterprise Integration Pattern (EIP) lets you invoke POJO methods directly within routes, seamlessly integrating custom business logic without heavy frameworks. This article presents a fully self-contained, runnable Java example using Camel Main, featuring header manipulation and enhanced logging for real-world demonstration. The code requires only camel-core on the classpath.

Key Features Demonstrated

  • Timer-triggered message flow every 2 seconds.
  • Bean method invocation with body and headers parameter binding.
  • Custom header enrichment (processor metadata, timestamps).
  • Split routes using direct for modularity.
  • Infinite runtime via Camel Main (Ctrl+C to stop).

The bean processes the message body, uppercases it, adds headers tracking processing details, and returns the transformed body.

Complete Runnable Code

Here's the entire application in a single file—compile and run directly:

import org.apache.camel.Body;
import org.apache.camel.Headers;
import org.apache.camel.builder.RouteBuilder;

import java.util.Map;

/**
 * Apache Camel example demonstrating bean method invocation in a route.
 * Uses a timer-based route that processes messages through a custom bean.
 */
public class BeanExampleApp {

    static void main(String[] args) throws Exception {
        // Create a new Camel Main instance (using fully qualified name to avoid conflict)
        org.apache.camel.main.Main main = new org.apache.camel.main.Main();

        // Add routes with bean processing
        main.configure().addRoutesBuilder(new RouteBuilder() {
            @Override
            public void configure() {
                // Route 1: Timer that fires every 2 seconds
                from("timer:beanTick?period=2000")
                        .setBody().constant("hello from timer")
                        .log("${body}")
                        .to("direct:process")
                        .log("${body}");

                // Route 2: Process the message using a bean
                from("direct:process")
                        .bean(new MyProcessor(), "process")  // Invoke bean method
                        .log("After bean: ${body}")
                        .log("Headers - ProcessedBy: ${header.ProcessedBy}, ProcessedAt: ${header.ProcessedAt}, OriginalBody: ${header.OriginalBody}");
            }
        });

        // Run Camel (will run until Ctrl+C is pressed)
        main.run(args);
    }

    /**
     * Custom processor bean that processes messages.
     * Accepts body as String and headers as Map for manipulation.
     */
    public static class MyProcessor {
        public String process(@Body String body, @Headers Map<String, Object> headers) {
            // Add custom headers
            headers.put("ProcessedBy", "MyProcessor");
            headers.put("ProcessedAt", System.currentTimeMillis());
            headers.put("OriginalBody", body);

            // Process and return the modified body
            return body.toUpperCase() + " - processed by MyProcessor";
        }
    }
}

Expected Output

Console logs repeat every 2 seconds:

hello from timer
After bean: HELLO FROM TIMER - processed by MyProcessor
Headers - ProcessedBy: MyProcessor, ProcessedAt: 1769775068470, OriginalBody: hello from timer
HELLO FROM TIMER - processed by MyProcessor

Best Practices Highlighted

  • Parameter Binding: Use annotations like @Body, @Headers, @Header("name") for type-safe access; raw types work for simple cases.

  • Method Selection: Explicitly name "process" method to avoid ambiguity with overloads.

  • Inline Beans: Perfect for simple processors; use registry lookup for complex or shared beans.

  • Route Modularity: direct: endpoints enable clean separation of concerns.

This pattern excels for transformations, validations, and custom logic in enterprise integration routes.