Apache Camel's Dead Letter Channel (DLC) is an Enterprise Integration Pattern that gracefully handles failed messages by routing them to a designated endpoint, preventing route blockages. This article explores DLC configuration using a lightweight log endpoint—no JMS broker required—ideal for development, testing, or simple logging setups. You'll get a complete, runnable example to see it in action.

What is Dead Letter Channel?

The DLC activates when a message processing fails after exhausting retries. Unlike Camel's default error handler, which logs exceptions, DLC preserves the original input message and forwards it to a "dead letter" endpoint like a log, file, or queue. Key benefits include message isolation for later analysis and non-blocking route behavior.

It supports redelivery policies for automatic retries before final routing. This pattern shines in enterprise integration where failures (e.g., network issues, data validation errors) must not halt overall flow.

Why Use Log Endpoint?

The log component offers zero-setup error handling: it captures failures at ERROR level with full exchange details (body, headers, exception stack trace). Perfect for:

  • Quick debugging without external dependencies.
  • Console output in standalone apps or containers.
  • Custom formatting via options like showAll=true or multiline=true.

No database, file system, or broker needed—run it anywhere Camel supports logging (SLF4J/Logback by default).

Complete Working Example

This standalone Java app uses Camel Main to simulate failures via a timer-triggered route. It retries twice, then logs the failure.

Java DSL Code

import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.main.Main;

public class DeadLetterLogDemo extends RouteBuilder {
    @Override
    public void configure() {
        // Configure DLC with log endpoint
        errorHandler(deadLetterChannel("log:dead?level=ERROR&showAll=true&multiline=true")
            .useOriginalMessage()  // Preserves original input
            .maximumRedeliveries(2)
            .redeliveryDelay(1000)
            .retryAttemptedLogLevel(org.apache.camel.LoggingLevel.WARN));

        // Route that simulates failure
        from("timer:fail?period=5000&repeatCount=1")
            .log("🔥 Starting processing: ${body}")
            .process(exchange -> {
                // Simulate a runtime failure (e.g., bad data parsing)
                throw new RuntimeException("💥 Simulated processing error!");
            })
            .log("✅ Success log (won't reach here)");
    }

    public static void main(String[] args) throws Exception {
        Main main = new Main();
        main.configure().addRoutesBuilder(new DeadLetterLogDemo());
        main.run(args);  // Runs until Ctrl+C
    }
}

Expected Console Output

🔥 Starting processing:
Failed delivery for (MessageId: 25B7DFF9FB26DD8-0000000000000000 on ExchangeId: 25B7DFF9FB26DD8-0000000000000000). On delivery attempt: 0 caught: java.lang.RuntimeException: 💥 Simulated processing error!
Failed delivery for (MessageId: 25B7DFF9FB26DD8-0000000000000000 on ExchangeId: 25B7DFF9FB26DD8-0000000000000000). On delivery attempt: 1 caught: java.lang.RuntimeException: 💥 Simulated processing error!
Failed delivery for (MessageId: 25B7DFF9FB26DD8-0000000000000000 on ExchangeId: 25B7DFF9FB26DD8-0000000000000000). On delivery attempt: 2 caught: java.lang.RuntimeException: 💥 Simulated processing error!
Exchange[
  Id: 25B7DFF9FB26DD8-0000000000000000
  RouteGroup: null
  RouteId: route1
  ExchangePattern: InOnly
  Properties: {CamelExceptionCaught=java.lang.RuntimeException: 💥 Simulated processing error!, CamelFailureRouteId=route1, CamelFatalFallbackErrorHandler=[route1], CamelToEndpoint=log://dead?level=ERROR&multiline=true&showAll=true}

Advanced Customizations

Enhance with processors for richer logging:

errorHandler(deadLetterChannel("log:dead?level=ERROR")
    .onExceptionOccurred(exchange -> {
        exchange.getIn().setHeader("failureTime", System.currentTimeMillis());
        exchange.getIn().setHeader("errorDetails", exchange.getException().getMessage());
    }));

Best Practices

  • Always pair with useOriginalMessage() to avoid losing input data.
  • Set maximumRedeliveries=0 for immediate DLC without retries in tests.
  • Monitor log volume in production; consider rotating to file or alerting.
  • Test edge cases: network timeouts, serialization errors, poison messages.
  • Global DLC via camelContext.setErrorHandlerFactory() for app-wide coverage.

This setup provides robust, observable error handling. Experiment by changing the failure to a real processor (e.g., invalid JSON parsing) for your use cases.