Getting Started with Camel: Error Handling

Error handling is tricky. Not because it’s especially hard to do, but because everyone (operations, the business team, fellow programmers) seems to have a different idea of how a particular situation should be handled. A web service is down? No problem. You should try again every five seconds, but no more than 10 times. If the service doesn’t respond, send Operations an email, but don’t send me an email every time you fail to message it, just the 10th time.

These special requests result in “little gems” of code that are sprinkled throughout your application. They’re really important when everything is going wrong and ignored the rest of the time. It’s a shame really, some of the ridiculous stuff above is harder to write (and test) than some of the production code we’ve all written.

I know I’ve said this before, but I like Camel. It makes all the silly requests above trivial, and it gives me a mechanism for testing that I wired everything up correctly. Let’s look at a few ways to deal with errors that occur in your Camel routes.

Try/Catch/Finally

The simplest way to handle errors is also the most familiar.

public class DoTryRoute extends SpringRouteBuilder {
    private String from;
    private String to;
    private String error;
 
    @Override
    public void configure() throws Exception {
        from(from).id(from)
                .routeId(DoTryRoute.class.getSimpleName())
                .doTry()
                    .to(to).id(to)
                .doCatch(RuntimeException.class)
                    .to(error).id(error)
                .doFinally()
                    .log("done")
                .end();
    }
}

Fortunately, all this code should look familiar, so I won’t dwell on it too much. The better stuff is still coming.

On Exception

Camel has another way of handling exceptions that allow you to define what looks like a second route definition and feed your Exchange (message) into it.

public class OnExceptionRoute extends SpringRouteBuilder {
    private String from;
    private String to;
    private String error;
 
    @Override
    public void configure() throws Exception {
        onException(RuntimeException.class).handled(true).to(error).id(error);
 
        from(from)
            .to(to)
                .routeId(OnExceptionRoute.class.getSimpleName());
    }
}

In this example, we are defining an OnException handler that is route-scoped, meaning it will only handle Runtime Exceptions (or those that extend it) that are thrown from within this route. Runtime Exceptions thrown from other routes are not caught in this block and will need to define their own handlers. In the Spring DSL, you can also easily define global exception handlers that will catch exceptions for all your routes. The Camel documentation contains a simple example of how to write an exception handler that is globally scoped.

If you don’t want to continue processing after your exchange has gone down the OnException route, then you will need to make sure that you mark the exception as “handled” (as shown above); otherwise, you will see your exchange continue down the rest of your route. When an exception is handled, you are effectively implementing a dead letter channel.

Dead Letter Channel

On exception is a good way to handle specific exceptions that you know are thrown by a method, but what if you want to define a route that will handle any exception that pops up, and treat that message as complete by default. If that’s the case, you want a Dead Letter Channel. The Dead Letter Channel is just what it sounds like, a place to go when an unhandled error occurs, where you can ensure that all of your final processing takes place. For example, you may want to put all of your failed messages (for any reason) in a specific directory, but before you do that, you want to log the contents of the error to a log file or database. All of this can be done with a Dead Letter Channel.

Dead Letter Channels and OnException clauses are extremely similar, but the difference is that a Dead Letter Channel does not require you to define the type of exception you are catching, and you don’t have to worry about that pesky “handled” syntax that I described above.

The real power in Dead Letter Channels and OnException handlers comes from their ability to retry when a failure occurs using a Redelivery Policy. A Redelivery Policy includes two key pieces that will make your error handling a lot more robust: the maximum number of retries and the ability to delay (in millis) between those retries. These two features alone are a major reason to use Camel, and they make handling unreliable services possible. Here’s an example of a route with a simple Dead Letter Channel defined.

public class DeadLetterRoute extends SpringRouteBuilder {
    private String from;
    private String to;
    private String error;
    private int retries;
 
    @Override
    public void configure() throws Exception {
        errorHandler(deadLetterChannel(error).maximumRedeliveries(retries));
 
        from(from)
            .to(to)
                .routeId(DeadLetterRoute.class.getSimpleName());
    }
}

I’ve been working with Camel for a little over a year now, and have been unable to find a scenario that I could not handle with one of the above error handling mechanisms. Hopefully you will have the same success.

If you are interested in the code above or seeing how you can test drive your way to this code, then check out the error-handling branch of our project on SourceAlliesBlog’s GitHub repository. If you’d like further Camel reading, check out Camel in Action by Claus Ibsen, who happens to be one of the main posters you’ll see over on the Camel message boards.

One comment

Comments are closed.