Apache Camel's robust error handling ensures reliable integration routes even when failures occur. The DefaultErrorHandler provides automatic retries and logging, while onException clauses offer precise control over specific exceptions.

Core Concepts

DefaultErrorHandler is Camel’s default strategy for all routes, featuring retry logic, detailed logging, and flexible recovery paths. It logs exceptions at ERROR level by default and supports configurable redeliveries without requiring dead letter channels.

onException clauses intercept specific exception types globally or per-route, with priority based on exception specificity—most-derived classes match first. Use handled(true) to stop propagation and transform responses, or continued(true) to resume the route.

DefaultErrorHandler Configuration

Set global retry policies early in RouteBuilder’s configure() method:

errorHandler(defaultErrorHandler()
    .maximumRedeliveries(3)
    .redeliveryDelay(2000)
    .retryAttemptedLogLevel(LoggingLevel.WARN)
    .logStackTrace(true));

Key options control retry behavior: maximumRedeliveries caps attempts, redeliveryDelay sets wait time (with exponential backoff), and retryAttemptedLogLevel adjusts retry logging verbosity. Per-route overrides nest within from() endpoints for granular control.

onException Best Practices

Place onException handlers before route definitions for global scope. Multiple handlers for the same exception type chain by priority, with conditions via onWhen():

onException(IOException.class)
    .handled(true)
    .log("🚨 IO EXCEPTION: ${exception.message}")
    .to("direct:ioErrorHandler");

onException(IllegalArgumentException.class)
    .onWhen(exchange -> "high".equals(exchange.getIn().getHeader("error.priority")))
    .handled(true)
    .log("⚠️ HIGH PRIORITY: ${exception.message}");

The handled(true) flag clears the exception and prevents DefaultErrorHandler retries unless continued(true) is specified. Route to dedicated error flows like direct:ioErrorHandler for consistent processing.

Example

This self-contained demo showcases layered error handling across exception types, conditional logic, and retry policies:

import org.apache.camel.*;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;

public class CamelErrorHandlingDemo extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        // Global DefaultErrorHandler with retries
        errorHandler(defaultErrorHandler()
            .maximumRedeliveries(3)
            .redeliveryDelay(2000)
            .retryAttemptedLogLevel(LoggingLevel.WARN)
            .logStackTrace(true));

        // Specific exception handlers (priority: most specific first)
        onException(java.io.IOException.class)
            .handled(true)
            .log("🚨 IO EXCEPTION HANDLED FIRST: ${exception.message}")
            .to("direct:ioErrorHandler");

        onException(IllegalArgumentException.class)
            .maximumRedeliveries(2)
            .redeliveryDelay(1000)
            .onWhen(exchange -> "high".equals(exchange.getIn().getHeader("error.priority")))
            .handled(true)
            .log("⚠️ HIGH PRIORITY ARG EXCEPTION: ${exception.message}")
            .to("direct:argErrorHandler");

        onException(IllegalArgumentException.class)
            .handled(true)
            .log("⚠️ DEFAULT ARG EXCEPTION: ${exception.message}")
            .to("direct:argErrorHandler");

        onException(Exception.class)
            .handled(true)
            .log("💀 GENERIC EXCEPTION: ${exception.message}")
            .to("direct:deadLetterQueue");

        // Main processing route
        from("direct:start")
            .routeId("mainRoute")
            .log("Processing: ${body}")
            .process(mainProcessor())
            .to("direct:success");

        from("direct:success").log("✅ SUCCESS: ${body}");
        from("direct:ioErrorHandler").log("📁 IO Error handled").setBody(constant("IO_PROCESSED"));
        from("direct:argErrorHandler").log("🔄 Arg error processed").setBody(constant("ARG_PROCESSED"));
        from("direct:deadLetterQueue").log("⚰️ DLQ: ${exception.stacktrace}").setBody(constant("DLQ_PROCESSED"));
    }

    private static Processor mainProcessor() {
        return exchange -> {
            String input = exchange.getIn().getBody(String.class);
            switch (input != null ? input : "") {
                case "io-fail":
                    exchange.getIn().setHeader("error.priority", "high");
                    throw new java.io.IOException("💾 Disk full");
                case "arg-high":
                    exchange.getIn().setHeader("error.priority", "high");
                    throw new IllegalArgumentException("❌ High priority invalid data");
                case "arg-normal":
                    throw new IllegalArgumentException("❌ Normal invalid data");
                case "generic-fail":
                    throw new RuntimeException("Unknown error");
                default:
                    exchange.getIn().setBody("✅ Processed: " + input);
            }
        };
    }

    public static void main(String[] args) throws Exception {
        try (CamelContext context = new DefaultCamelContext()) {
            context.getCamelContextExtension().setName("CamelErrorDemo");
            context.addRoutes(new CamelErrorHandlingDemo());
            context.start();

            ProducerTemplate template = context.createProducerTemplate();
            System.out.println("🚀 Camel Error Handling Demo...\n");

            template.sendBody("direct:start", "io-fail");      // → IO handler
            template.sendBody("direct:start", "arg-high");     // → High priority arg
            template.sendBody("direct:start", "arg-normal");   // → Default arg  
            template.sendBody("direct:start", "generic-fail"); // → Generic handler
            template.sendBody("direct:start", "good");         // → Success

            Thread.sleep(8000);
        }
    }
}

Expected Demo Output

textProcessing: io-fail
🚨 IO EXCEPTION HANDLED FIRST: 💾 Disk full
📁 IO Error handled

Processing: arg-high  
⚠️ HIGH PRIORITY ARG EXCEPTION: ❌ High priority invalid data
🔄 Arg error processed

Production Tips

Test all failure paths during development using predictable payloads like the demo. Monitor exchange.getProperty(Exchange.REDELIVERY_COUNTER) for retry stats. For complex flows, combine with doTry().doCatch() blocks for local exception handling within routes. This approach scales from simple REST APIs to enterprise integration patterns.