IMPORTANT We strongly advise to read the book "Camel in Action - second edition" prior to creating Integration Flows, in order to have a better understanding of the principles of Apache Camel.
Understanding error handling
When it comes to errors, we can divide them into recoverable and irrecoverable errors. An irrecoverable error is an error that remains an error now matter how many times you try to perform the same action again. A recoverable error, on the other hand, is a temporary error that might not cause a problem on the next attempt. A good example of such an error is a problem with the network connection resulting in a java.io.IOException. On a subsequent attempt, the network issue could be resolved and your application could continue to operate.
In Camel, a recoverable error is represented as a plain Throwable or Exception that can be set or accessed from org.apache.camel.Exchange
An irrecoverable error is represented as a message with a fault flag that can be set or accessed from org.apache.camel.Exchange
. The fault flag must be set using the setFault(true) method.
Camel can catch and handle all exceptions that are thrown. Camel’s error handling can then determine how to deal with the errors—retry, propagate the error back to the caller, or do something else. End users of Camel can set irrecoverable errors as fault messages, and Camel can react accordingly and stop routing the message. - Exceptions are represented as recoverable errors. - Fault messages are represented as irrecoverable errors.
Camel regards all exceptions as recoverable and stores them on the exchange using the setException(Throwable cause) method. This means error handlers in Camel will only react to exceptions set on the exchange. By default, they won’t react if an irrecoverable error has been set as a fault message.
Where Camel’s error handling applies
Camel’s error handling doesn’t apply everywhere. To understand why, take a look at following picture:
Error handlers in Camel
Error handlers in Camel will only react to exceptions set on the exchange. By default, they won’t react if an irrecoverable error has been set as a fault message. The rule of thumb is that error handlers in Camel only trigger when exchange.getException() != null
.
The Channel is in between each node of the route path, which ensures it can act as a controller that monitors and controls the routing at runtime. This is the feature that allows Camel to enrich the route with error handling, message tracing, interceptors, and much more. For now, you just need to know that this is where the error handler lives. Check out following example:
<route id="main">
<from id="direct-start" uri="direct:start" />
<to id="request-validation" uri="validator:xsd/vecozo.xsd"/>
<to id="http-call" uri="https://webservice.com/v1/VZ801802.svc/soap11?bridgeEndpoint=true" />
</route>
Imagine that an exception was thrown from the request-validation processor during invocation of the validate method. Then the processor 3
would throw an exception, which would be propagated back to the previous channel 2
, where the error handler would catch it. This gives Camel the chance to react accordingly. For example, Camel could try again (redeliver), or it could route the message to another route path (detour using exception policies), or it could give up and propagate the exception back to the caller. With the default settings, Camel will propagate the exception back to the caller.
The default error handler
Camel is preconfigured to use the DefaultErrorHandler
, which covers most use cases. The default error handler is preconfigured and doesn’t need to be explicitly declared in the route. In every Camel route, there is a Channel that sits between each node in the route graph. The default error handler is configured with these settings:
- No redelivery
- Exceptions are propagated back to the caller
The dead letter channel error handler
The DeadLetterChannel error handler is similar to the default error handler except for the following differences: - The dead letter channel is the only error handler that supports moving failed messages to a dedicated error queue, which is known as the dead letter queue. - Unlike the default error handler, the dead letter channel will, by default, handle exceptions and move the failed messages to the dead letter queue. - The dead letter channel supports using the original input message when a message is moved to the dead letter queue.
This pattern is often used with messaging. Instead of allowing a failed message to block new messages from being picked up, the message is moved to a dead letter queue to get it out of the way.
Features of the error handlers
Feature | Description |
---|---|
Redelivery policies | Redelivery policies allow you to define policies for whether or not redelivery should be attempted. The policies also define settings such as the maximum number of redelivery attempts, delays between attempts, and so on. |
Exception policies | Exception policies allow you to define special policies for specific exceptions. |
Error handling | This option allows you to specify whether or not the error handler should handle the error. You can let the error handler deal with the error or leave it for the caller to handle. |
Using error handlers with redelivery
Communicating with remote servers relies on network connectivity that can be unreliable and have outages. Luckily these disruptions cause recoverable errors—the network connection could be reestablished in a matter of seconds or minutes. Remote services can also be the source of temporary problems, such as when the service is restarted by an administrator. To help address these problems, Camel supports a redelivery mechanism that allows you to control how recoverable errors are dealt with. A redelivery policy defines how and whether redelivery should be attempted. Configuring this in Spring XML is done as follows:
<errorHandlers>
<errorHandler xmlns="http://camel.apache.org/schema/spring" id="eh" type="DefaultErrorHandler">
<redeliveryPolicy maximumRedeliveries="2" redeliveryDelay="2000"/>
</errorHandler>
<!-- double the delay between redelivery attempts, starting with 250 milliseconds and ending with 2 seconds -->
<errorHandler xmlns="http://camel.apache.org/schema/spring" id="eh-http" type="DefaultErrorHandler">
<redeliveryPolicy maximumRedeliveries="4" redeliveryDelay="250"
backOffMultiplier="2" useExponentialBackOff="true"/>
</errorHandler>
</errorHandlers>
In the example above two error handlers are configured. First error handler with id "eh" is of type DefaultErrorHandler and is configured to redeliver up to 2 times with 2-second delays. The second error handler is of type DefaultErrorHandler and is configured to redeliver up to 4 times, but to double the delay between redelivery attempts, starting with 250 milliseconds and ending with 2 seconds.
There are two things to notice in this Spring XML configuration. By using the type option on the errorHandler
tag, you select which type of error handler to use. In this example, it’s the default error handler. You also have to enable exponential backoff explicitly by setting the useExponentialBackOff option to true.
A redelivery policy defines how and whether redelivery should be attempted. The following table outlines the most commonly used options supported by the redelivery policy and the default settings:
Option | Type | Default | Description |
---|---|---|---|
backOffMultiplier | double | 2.0 | Exponential back-off multiplier used to multiply each consequent delay. redeliveryDelay is the starting delay. Exponential back-off is disabled by default |
collisionAvoidanceFactor | double | 0.15 | A percentage to use when calculating a random delay offset (to avoid using the same delay at the next attempt). Will start with the redeliveryDelay as the starting delay. Collision avoidance is disabled by default |
logExhausted | boolean | true | Specifies whether the exhaustion of redelivery attempts (when all redelivery attempts have failed) should be logged |
logExhaustedMessageBody | boolean | false | Specifies whether the message history should include the message body and headers. This is turned off by default to avoid logging content from the message payload, which can carry sensitive data |
logExhaustedMessageHistory | boolean | Specifies whether the message history should be logged when logging is exhausted. This option is by default true for all error handlers, except the dead letter channel, where it’s false | |
logRetryAttempted | boolean | true | Specifies whether redelivery attempts should be logged |
logStackTrace | boolean | true | Specifies whether stacktraces should be logged when all redelivery attempts have failed |
maximumRedeliveries | int | 0 | Maximum number of redelivery attempts allowed. 0 is used to disable redelivery, and -1 will attempt redelivery forever until it succeeds |
maximumRedeliveryDelay | long | 60000 | An upper bound in milliseconds for redelivery delay. This is used when you specify nonfixed delays, such as exponential back-off, to avoid the delay growing too large |
redeliveryDelay | long | 1000 | Fixed delay in milliseconds between each redelivery attempt |
useExponentialBackOff | boolean | false | Specifies whether exponential back-off is in use |
Scope for error handlers
U4IK Integration Flows support route scoped error handlers, so you can configure a route-scoped error handler that applies only for a particular route. in the example below the main route applies the "eh" error handler and the http-call-route uses the "eh-http" error handler:
<routes xmlns="http://camel.apache.org/schema/spring" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<route id="main" errorHandlerRef="eh">
</route>
<route id="http-call-route" errorHandlerRef="eh-http">
</route>
</routes>
Handling faults
We mentioned earlier that by default the Camel error handlers will only react to exceptions. Because a fault isn’t represented as an exception but as a message that has the fault flag enabled, faults will not be recognized and handled by Camel error handlers. There may be times when you want the Camel error handlers handle faults as well.
You can enable fault handling in Spring XML as follows:
<route id="http-call-route" handleFault="true" errorHandlerRef="eh-http">
</route>
Once fault handling is enabled, the Camel errors handlers will recognize the faults and react. Under the hood, the fault is converted into an Exception with the help of an interceptor.
TIP: You can enable fault handling to let Camel error handlers react to faults returned from components such as CXF, SOAP, JBI, or NMR.
Using exception policies
Exception policies are used to intercept and handle specific exceptions in particular ways. For example, exception policies can influence, at runtime, the redelivery policies the error handler is using. They can also handle an exception or even detour a message.
Using onException
When an exception policy has been selected, its configured policy will be used by the error handler. In the example below, the route has an error handler with id "eh" and type DefaultErrorHandler and the error handler is configured to redeliver up to 2 times with 2-second delays. However, there is also an extra exception policy configured within the route, where there is no redelivery configured there and explicitly mentioned that the exception will be handled by the policy.
Because the OnException is configured to handle exceptions—handled(true)—Camel will break out from continuing the routing and will return the failure message to the initial consumer, which in turn returns the custom reply message.
So, in the example below, when a SchemaValidationException is thrown, the exception will not be handled by the default error handler "eh", but will always be handled according to the onException policy. If you would not set handled(true), then any value configured on the exception policy will override options configured on the error handler. So if the error handler has the redelivery option configured with maximumRedeliveries="2" and the onException has the same option configured, its value of 1 in the example below will be used instead.
TIP: When you configure redelivery policies (with onException), they override the existing redelivery policies set in the current error handler. This is convention over configuration, because you only need to configure the differences, which is often just the number of redelivery attempts or a different redelivery delay.
<routes xmlns="http://camel.apache.org/schema/spring" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<route id="main"errorHandlerRef="eh">
<from id="direct-start" uri="direct:start" />
<onException>
<exception>org.apache.camel.support.processor.validation.SchemaValidationException</exception>
<redeliveryPolicy maximumRedeliveries="1" redeliveryDelay="250" />
<handled><constant>true</constant></handled>
<setHeader id="set-CamelHttpResponseCode-header-for-validation-error" name="CamelHttpResponseCode">
<constant>400</constant>
</setHeader>
<setBody id="set-validation-error-body">
</setBody>
</onException>
</route>
</routes>
<errorHandlers>
<errorHandler xmlns="http://camel.apache.org/schema/spring" id="eh" type="DefaultErrorHandler">
<redeliveryPolicy maximumRedeliveries="2" redeliveryDelay="2000" />
</errorHandler>
</errorHandlers>
IMPORTANT onException elements have to be configured in the route right after the
from
component. Else the Integration Flow definition will not validate and cannot be published.
Multiple exceptions per onException
So far, you’ve only seen examples with one exception per onException, but you can define multiple exceptions in the same onException:
<onException>
<exception>javax.xml.xpath.XPathException</exception>
<exception>javax.xml.transform.TransformerException</exception>
<to uri="log:xml?level=WARN"/>
</onException>
Ignoring exceptions
onException can handle exceptions. Handling an exception means that Camel will break out of the route. But there are times when all you want is to catch the exception and continue routing. This is possible to do in Camel using continued. All you have to do is to use continued(true) instead of handled(true). Suppose we want to ignore any ValidationException which may be thrown in the route:
<onException>
<exception>org.apache.camel.processor.validation.SchemaValidationException</exception>
<continued><constant>true</constant></continued>
</onException>
Using doTry, doCatch and doFinally
Camel has a counterpart to the classic try ... catch ... finally block in its DSL: doTry ... doCatch ... doFinally. The doTry ... doCatch block is a bit of a sidetrack, but it’s useful because it helps bridge the gap between thinking in regular Java code and thinking in EIPs.
A difference between doCatch and onException is that doCatch will handle the exception, whereas onException will, by default, not handle it.
The doTry ... doCatch block is only route scoped. The blocks only work in the route in which they’re defined.
In the following example a doTry ... doCatch block is placed within a route. The catch block is placed after all steps, so if an exception is thrown, e.g. a validation or transformation exception, the catch block will be executed. So if the validator
processor fails, the catch block is executed and since that's the last part of the route nothing else will happen:
<route id="main">
<from id="direct-start" uri="direct:start" />
<doTry id="try">
<to id="request-validation" uri="validator:xsd/vecozo.xsd"/>
<to id="request-transformation" uri="xslt:xslt/to-vecozo.xsl" />
<to id="to-http-route" uri="direct:http-call-route"/>
<!-- Convert body from stream to string to make sure Flow History can show content properly -->
<convertBodyTo id="convert-body-to-string" type="java.lang.String"/>
<to id="response-transformation" uri="xslt:xslt/from-vecozo.xsl" />
<doCatch>
<exception>org.apache.camel.support.processor.validation.SchemaValidationException</exception>
<exception>javax.xml.transform.TransformerException</exception>
<setHeader id="set-CamelHttpResponseCode-header-for-validation-error" name="CamelHttpResponseCode">
<constant>400</constant>
</setHeader>
<setBody id="set-validation-error-body">
<simple><![CDATA[<searchError xmlns="http://u4ik.unit4.com/integration-flow/vecozo/searchResponse">
<code>1010</code>
<description>Input document is invalid</description>
<details>${exchangeProperty.CamelExceptionCaught}</details>
</searchError>]]></simple>
</setBody>
</doCatch>
</doTry>
</route>
If the doCatch block would not be placed at the end of the route, the processors placed after the catch block are still executed:
<route id="main">
<from id="direct-start" uri="direct:start" />
<doTry id="try">
<to id="request-validation" uri="validator:xsd/vecozo.xsd"/>
<doCatch>
<exception>org.apache.camel.processor.validation.SchemaValidationException</exception>
<setHeader id="set-CamelHttpResponseCode-header-for-validation-error" name="CamelHttpResponseCode">
<constant>400</constant>
</setHeader>
<setBody id="set-validation-error-body">
<simple><![CDATA[<searchError xmlns="http://u4ik.unit4.com/integration-flow/vecozo/searchResponse">
<code>1010</code>
<description>Input document is invalid</description>
<details>${exchangeProperty.CamelExceptionCaught}</details>
</searchError>]]></simple>
</setBody>
</doCatch>
</doTry>
<to id="request-transformation" uri="xslt:xslt/to-vecozo.xsl" />
<to id="to-http-route" uri="direct:http-call-route"/>
<!-- Convert body from stream to string to make sure Flow History can show content properly -->
<convertBodyTo id="convert-body-to-string" type="java.lang.String"/>
<to id="response-transformation" uri="xslt:xslt/from-vecozo.xsl" />
</route>
In the example above, when a validation exception is thrown, the catch block is executed and after that the route will contine with the xslt
step.
Example implementing an error handler solution
In the following example the remote HTTP server used for uploading files is unreliable, so therefore a secondary failover is implemented to transfer the files by FTP to a remote FTP server. onException is used to provide this kind of feature:
<routes xmlns="http://camel.apache.org/schema/spring" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<route id="main"errorHandlerRef="eh">
<from id="direct-start" uri="direct:start" />
<onException>
<exception>IOException.class</exception>
<redeliveryPolicy maximumRedeliveries="3" />
<handled><constant>true</constant></handled>
<to id="sftp-transfer" uri="ftp://gear@ftp.rider.com?password=secret" />
</onException>
<to id="http-call" uri="http://rider.com/upload?user=gear&password=secret" />
</route>
</routes>
<errorHandlers>
<errorHandler xmlns="http://camel.apache.org/schema/spring" id="eh" type="DefaultErrorHandler">
<redeliveryPolicy maximumRedeliveries="5" redeliveryDelay="10000" />
</errorHandler>
</errorHandlers>
This example adds onException to the route, telling Camel that in the case of IOException, it should try redelivering up to three times, using a 10-second delay. If there’s still an error after the redelivery attempts, Camel will handle the exception and reroute the message to the FTP endpoint instead. The power and flexibility of the Camel routing engine shines here. onException is just another route, and Camel will continue on this route instead of the original route.