Error Handling in Apache Camel
This post will discuss some thoughts on Apache Camel and its error handling features. Some parts are familiar from the main documentation and others are just practices I use myself. Let’s get started.
These are errors you can recover from, for example, network connection errors. These are usually Exceptions and Camel catches these and puts them on the Exchange.
These are errors that remain errors no matter how many times you retry. Examples include missing database tables. These are usually represented as message faults Camel does not attempt to recover from them.
- DefaultErrorHandler – Default and automatically enabled
- DeadLetterChannel – Implemented dead letter channel EIP – send message to a backout queue
- TransactionErrorHandler – Used for handling transactions.
- LoggingErrorHandler – Simply log the exception.
- NoErrorHandler – Disable error handling.
The default error handler is enabled by default. There is no redelivery enabled and exceptions are sent back to the caller. The original message is usually discarded unless specified otherwise. The dead letter channel error handler moves failed messages to a dedicated error queue. It does provide handling of exceptions and you can extend it with retry functionality. By default, Camel suppresses exceptions, removes them from the exchange and puts them as properties on the exchange.
I would say in most causes you will be using the default error handler or the dead letter channel error handler.
Asynchronous and Synchronous Error handling
There are two types of communication modes that determine how an integration showed respond to errors. These are asynchronous and synchronous communication.
In asynchronous mode, when an error occurs the behavior of the error handling will primarily use the Dead Letter Channel Error Handler together with OnException behavior. It can be described as follows:
- The CamelContext should have OnException, exception and redelivery policy defined. These determine how Camel should react when an error occurs and whether Camel should attempt redelivery based on some policy.
- Finally if the message could not be redelivered it should be routed to another route for being posted to a backout queue. This is to preserve the original message in order to examine it further and to allow redelivery at a later stage if possible.
- Relevant headers need to be written to the exchange so that the error handler can capture these and put them as metadata on the message being sent to the backout queue.
- Once the message has been routed to the error handler route, then relevant error headers are set and the route uses the backout queue name set on the header to route the message to the backout queue. Physically, the backout queue can be a queue in RabbitMQ, ActiveMQ or some other queuing engine which Camel can speak to. ActiveMQ is quit suitable because it can be installed as a feature in Karaf and Hawtio can connect it which makes it easy to view brokers and queues.
In synchronous mode the client is waiting for a response. Here Camel has excellent fine-tuned error handling so we can adapt the error message or behavior depending on the type of error that occurs. The error message can then be sent back to the client who has the final responsibility to either retry or stop the call. No message is sent to the backout queue since client is made aware of the error. The process can be described as follows:
- The CamelContext should have OnException and Exception policies defined for each type of error it wants to catch.
- The parameter handled will be set to true so the actual error is not returned to the client.
- Within the OnException you can add relevant headers and change the response body giving the client a more user-friendly error message than a stack trace.
- Do not underestimate error handling. It is by far more time consuming than sometimes writing the actual integration logic.
- Test your error handler in the unit tests so you ensure the behavior of the error handler matches your expectation. Discuss these expectations with the integration owner.
- Try to separate the error handling logic from the actual business logic. You can for instance have a route called ”errorHandler” and all it does is to accept a payload, verify that certain headers exist and puts the payload to a backout queue. You can off course set the queue name dynamically and let this route read the destination from the header. This way this route can be shared by all other routes that need this kind of error handling.
- Do not let clients of integrations know what actually went wrong. This in a sense means if something goes wrong in a route catch the error and provide some sensible error message to the client whilst writing the actual error in a log file or some where it can be analyzed. There are two main reasons for this.
- Clients don’t want to see a giant stack trace of errors and some systems cannot handle seeing this as a response.
- From security perspective it is not good to reveal the inner details of the integration.
- Decide on a range of metadata headers that should be part of all integrations and ensure these are set when you send a message to a backout queue.
- Have a backup plan if the backout queue is not available. If the ActiveMQ broker is down, what should you do? Write to a local file? Email it to the support?
- Finally, know that there are errors related to data and there are errors related to the actual functioning of the integration. Bad data can cause mapping errors. You need to catch these as well and react accordingly.
Hope you enjoyed and if you have questions just let me know. Next time I will try to cover a bit about logging.