Chapter 5. Error handling

This chapter covers

  • The difference between recoverable and irrecoverable errors
  • Where and when Camel’s error handling applies
  • The different error handlers in Camel
  • Using redelivery policies
  • Handling and ignoring exceptions with onException
  • Fine-grained control of error handling

In the last three chapters, we’ve covered three key functions that any integration kit should provide: routing, transformation, and mediation. In this chapter, we turn our focus to what happens when things go wrong. We want to introduce you to error handling early in this book, because we firmly believe that error handling should not be an afterthought but a key piece in your design from the start.

Writing applications that integrate disparate systems are a challenge when it comes to handling unexpected events. In a single system that you fully control, you can handle these events and recover. But systems that are integrated over the network have additional risks: the network connection could be broken, a remote system might not respond in a timely manner, or it might even fail for no apparent reason. Even on your local server, unexpected events can occur, such as the server’s disk filling up or the server running out of memory. Regardless of which errors occur, your application should be prepared to handle them.

In these situations, log files are often the only evidence of the unexpected event, so logging is important. Camel has extensive support for logging and for handling errors to ensure your application can continue to operate.

In this chapter, you’ll discover how flexible, deep, and comprehensive Camel’s error handling is and how to tailor it to deal with most situations. We’ll cover all the error handlers Camel provides out of the box, and when they’re best used, so you can pick the ones best suited to your applications. You’ll also learn how to configure and master redelivery, so Camel can try to recover from particular errors. We’ll also look at exception policies, which allow you to differentiate among errors and handle specific ones, and at how scopes can help you define general rules for implementing route-scoped error handling. Finally, we’ll look at what Camel offers when you need fine-grained control over error handling, so that it only reacts under certain conditions.

5.1. Understanding error handling

Before jumping into the world of error handling with Camel, we need to take a step back and look at errors more generally. There are two main categories of errors, recoverable and irrecoverable, and we need to look at where and when error handling starts, because there are some prerequisites that must happen beforehand.

5.1.1. Recoverable and irrecoverable errors

When it comes to errors, we can divide them into recoverable and irrecoverable errors, as illustrated in figure 5.1.

Figure 5.1. Errors can be categorized as either recoverable or irrecoverable. Irrecoverable errors continue to be errors on subsequent attempts; recoverable errors may be quickly resolved on their own.

An irrecoverable error is an error that remains an error now matter how many times you try to perform the same action again. In the integration space, that could mean trying to access a database table that doesn’t exist, which would cause the JDBC driver to throw an SQLException.

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 your daily life as a Java developer, you won’t encounter this division of errors into recoverable and irrecoverable often. Generally, exception handling code uses one of the two patterns illustrated in the following two code snippets.

The first snippet illustrates a common error-handling idiom, where all kinds of exceptions are considered irrecoverable and you give up immediately, throwing the exception back to the caller, often wrapped:

public void handleOrder(Order order) throws OrderFailedException {
try {
service.sendOrder(order);
} catch (Exception e) {
throw new OrderFailedException(e);
}
}

The next snippet improves on this situation by adding a bit of logic to handle redelivery attempts before eventually giving up:

Around the invocation of the service is the logic that attempts redelivery, in case an error occurs. After five attempts, it gives up and throws the exception.

What the preceding example lacks is logic to determine whether the error is recoverable or irrecoverable, and to react accordingly. In the recoverable case, you could try again, and in the irrecoverable case, you could give up immediately and rethrow the exception.

In Camel, a recoverable error is represented as a plain Throwable or Exception that can be set or accessed from org.apache.camel.Exchange using one of the following two methods:

void setException(Throwable cause);

or

Exception getException();

 

Note

The setException method on Exchange accepts a Throwable type, whereas the getException method returns an Exception type. getException also doesn’t return a Throwable type because of API compatibility.

 

An irrecoverable error is represented as a message with a fault flag that can be set or accessed from org.apache.camel.Exchange. For example, to set "Unknown customer" as a fault message, you would do the following:

Message msg = Exchange.getOut();
msg.setFault(true);
msg.setBody("Unknown customer");

The fault flag must be set using the setFault(true) method.

So why are the two types of errors represented differently? There are two reasons: First, the Camel API was designed around the Java Business Integration (JBI) specification, which includes a fault message concept. Second, Camel has error handling built into its core, so whenever an exception is thrown back to Camel, it catches it and sets the thrown exception on the Exchange as a recoverable error, as illustrated here:

try {
processor.process(exchange);
} catch (Throwable e) {
exchange.setException(e);
}

Using this pattern allows Camel to 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.

Now that you’ve seen recoverable and irrecoverable errors in action, let’s summarize how they’re represented in Camel:

  • Exceptions are represented as recoverable errors.
  • Fault messages are represented as irrecoverable errors.

Now let’s look at when and where Camel’s error handling applies.

5.1.2. Where Camel’s error handling applies

Camel’s error handling doesn’t apply everywhere. To understand why, take a look at figure 5.2.

Figure 5.2. Camel’s error handling only applies within the lifecycle of an exchange.

Figure 5.2 shows a simple route that translates files. You have a file consumer and producer as the input and output facilities, and in between is the Camel routing engine, which routes messages encompassed in an exchange. It’s during the lifecycle of this exchange that the Camel error handling applies. That leaves a little room on the input side where this error handling can’t operate—the file consumer must be able to successfully read the file, instantiate the Exchange, and start the routing before the error handling can function. This applies to any kind of Camel consumer.

So what happens if the file consumer can’t read the file? The answer is component-specific, and each Camel component must deal with this in its own way. Some components will ignore and skip the message, others will retry a number of times, and others will gracefully recover.

 

Note

There are a number of Camel components that provide minor error-handling features: File, FTP, Mail, iBATIS, RSS, Atom, JPA, and SNMP. These components are based on the ScheduledPollConsumer class, which offers a pluggable PollingConsumerPollStrategy that you can use to create your own error-handling strategy. You can learn more about this on the Camel website, at http://camel.apache.org/polling-consumer.html.

 

That’s enough background information—let’s dig into how error handling in Camel works. In the next section, we’ll start by looking at the different error handlers Camel provides.

5.2. Error handlers in Camel

In the previous section you learned that 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. The rule of thumb is that error handlers in Camel only trigger when exchange.getException() != null.

 

Note

In section 5.3.4, you’ll learn how you can instruct Camel error handlers to react to fault messages as well.

 

Camel provides a range of error handlers. They’re listed in table 5.1.

Table 5.1. The error handlers provided in Camel

Error handler

Description

DefaultErrorHandler This is the default error handler that’s automatically enabled, in case no other has been configured.
DeadLetterChannel This error handler implements the Dead Letter Channel EIP.
TransactionErrorHandler This is a transaction-aware error handler extending the default error handler. Transactions are covered in chapter 9 and are only briefly touched on in this chapter. We’ll revisit this error handler in chapter 9.
NoErrorHandler This handler is used to disable error handling altogether.
LoggingErrorHandler This error handler just logs the exception.

At first glance, having five error handlers may seem overwhelming, but you’ll learn that the default error handler is used in most cases.

The first three error handlers in table 5.1 all extend the RedeliveryErrorHandler class. That class contains the majority of the error-handling logic that the first three error handlers all leverage. The latter two error handlers have limited functionality and don’t extend RedeliveryErrorHandler.

We’ll look at each of these error handlers in turn.

5.2.1. The default error handler

Camel is preconfigured to use the DefaultErrorHandler, which covers most use cases. To understand it, consider the following route:

from("direct:newOrder")
.beanRef("orderService, "validate")
.beanRef("orderService, "store");

The default error handler is preconfigured and doesn’t need to be explicitly declared in the route. So what happens if an exception is thrown from the validate method on the order service bean?

To answer this, we need to dive into Camel’s inner processing, where the error handler lives. In every Camel route, there is a Channel that sits between each node in the route graph, as illustrated in figure 5.3.

Figure 5.3. A detailed view of a route path, where channels act as controllers between the processors

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.

Turning back to the example route, imagine that an exception was thrown from the order service bean during invocation of the validate method. In figure 5.3, the processor would throw an exception, which would be propagated back to the previous channel , 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 is configured with these settings:

  • No redelivery
  • Exceptions are propagated back to the caller

These settings match what happens when you’re working with exceptions in Java, so Camel’s behavior won’t surprise Camel end users.

Let’s continue with the next error handler, the dead letter channel.

5.2.2. 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.

Let’s look at each of these in a bit more detail.

The Dead Letter Channel

The DeadLetterChannel is an error handler that implements the principles of the Dead Letter Channel EIP. This pattern states that if a message can’t be processed or delivered, it should be moved to a dead letter queue. Figure 5.4 illustrates this pattern.

Figure 5.4. The Dead Letter Channel EIP moves failed messages to a dead letter queue.

As you can see, the consumer consumes a new message that is supposed to be routed to the processor . The channel controls the routing between and , and if the message can’t be delivered to , the channel invokes the deal letter channel error handler, which moves the message to the dead letter queue . This keeps the message safe and allows the application to continue operating.

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.

The same idea applies to the dead letter channel error handler in Camel. This error handler has an associated dead letter queue, which is based on an endpoint, allowing you to use any Camel endpoint you choose. For example, you can use a database, a file, or just log the failed messages.

When you choose to use the dead letter channel error handler, you must configure the dead letter queue as an endpoint so the handler knows where to move the failed messages. This is done a bit differently in the Java DSL and Spring XML. For example, here is how you’d log the message at ERROR level in Java DSL:

errorHandler(deadLetterChannel("log:dead?level=ERROR"));

And here is how you’d do it in Spring XML:

<errorHandler id="myErrorHandler" type="DeadLetterChannel"
deadLetterUri="log:dead?level=ERROR"/>

Now, let’s look at how the dead letter channel error handler handles exceptions when it moves the message to the dead letter queue.

Handling Exceptions by Default

By default, Camel handles exceptions by suppressing them; it removes the exceptions from the exchange and stores them as properties on the exchange. After a message has been moved to the dead letter queue, Camel stops routing the message and the caller regards it as processed.

When a message is moved to the dead letter queue, you can obtain the exception from the exchange using the Exchange.CAUSED_EXCEPTION property.

Exception e = exchange.getProperty(Exchange.CAUSED_EXCEPTION,
Exception.class);

Now let’s look at using the original message.

Using the Original Message with the Dead Letter Channel

Suppose you have a route in which the message goes through a series of processing steps, each altering a bit of the message before it reaches its final destination, as in the following code:

errorHandler(deadLetterChannel("jms:queue:dead"));

from("jms:queue:inbox")
.beanRef("orderService", "decrypt")
.beanRef("orderService", "validate")
.beanRef("orderService", "enrich")
.to("jms:queue:order");

Now imagine that an exception occurs at the validate method, and the dead letter channel error handler moves the message to the dead letter queue. Suppose a new message arrives and an exception occurs at the enrich method, and this message is also moved to the same dead letter queue. If you want to retry those messages, can you just drop them into the inbox queue?

In theory, you could do this, but the messages that were moved to the dead letter queue no longer match the messages that originally arrived at the inbox queue—they were altered as the messages were routed. What you want instead is for the original message content to have been moved to the dead letter queue, so that you have the original message to retry.

The useOriginalMessage option instructs Camel to use the original message when it moves messages to the dead letter queue. You configure the error handler to use the useOriginalMessage option as follows:

errorHandler(deadLetterChannel("jms:queue:dead").useOriginalMessage());

In Spring XML, you would do this:

<errorHandler id="myErrorHandler" type="DeadLetterChannel"
deadLetterUri="jms:queue:dead" useOriginalMessage="true"/>

Let’s move on to the transaction error handler.

5.2.3. The transaction error handler

The TransactionErrorHandler is built on top of the default error handler and offers the same functionality, but it’s tailored to support transacted routes. Chapter 9 focuses on transactions and discusses this error handler in detail, so we won’t say much about it here. For now, you just need to know that it exists and it’s a core part of Camel.

The remaining two error handlers are seldom used and are much simpler.

5.2.4. The no error handler

The NoErrorHandler is used to disable error handling. The current architecture of Camel mandates that an error handler must be configured, so if you want to disable error handling, you need to provide an error handler that’s basically an empty shell with no real logic. That’s the NoErrorHandler.

5.2.5. The logging error handler

The LoggingErrorHandler logs the failed message along with the exception. The logger uses standard log format from log kits such as log4j, commons logging, or the Java Util Logger.

Camel will, by default, log the failed message and the exception using the log name org.apache.camel.processor.LoggingErrorHandler at ERROR level. You can, of course, customize this.

That covers the five error handlers provided with Camel. Let’s now look at the major features these error handlers provide.

5.2.6. Features of the error handlers

The default, dead letter channel, and transaction error handlers are all built on the same base, org.apache.camel.processor.RedeliveryErrorHandler, so they all have several major features in common. These features are listed in table 5.2.

Table 5.2. Noteworthy features provided by 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.
Scope Camel error handlers have two possible scopes: context (high level) and route (low level). The context scope allows you to reuse the same error handler for multiple routes, whereas the route scope is used for a single route only.
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.

At this point, you may be eager to see the error handlers in action. In section 5.4.6 we’ll build a use case that introduces error handling, so there will be plenty of opportunities to try this on your own. But first, let’s look at the major features. We’ll look at redelivery and scope in section 5.3. Exception policies and error handling will be covered in section 5.4.

5.3. 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.

In this section, we’ll take a look at a real-life error-handling scenario, and then focus on how Camel controls redelivery and how you can configure and use it. We’ll also take a look at how you can use error handlers with fault messages. We’ll end this section by looking at error-handling scope and how it can be used to support multiple error handlers scoped at different levels.

5.3.1. An error-handling use case

Suppose you have developed an integration application at Rider Auto Parts that once every hour should upload files from a local directory to an HTTP server, and your boss asks why the files haven’t been updated in the last few days. You’re surprised, because the application has been running for the last month without a problem. This could well be a situation where neither error handling nor monitoring was in place.

Here’s the Java file that contains the integration route:

from("file:/riders/files/upload?delay=1h")
.to("http://riders.com?user=gear&password=secret");

This route will periodically scan for files in the /riders/files/upload folder, and if any files exist, it will upload them to the receiver’s HTTP server using the HTTP endpoint.

But there is no explicit error handling configured, so if an error occurs, the default error handler is triggered. That handler doesn’t handle the exception but instead propagates it back to the caller. Because the caller is the file consumer, it will log the exception and do a file rollback, meaning that any picked-up files will be left on the file system, ready to be picked up in the next scheduled poll.

At this point, you need to reconsider how errors should be handled in the application. You aren’t in major trouble, because you haven’t lost any files—Camel will only move successfully processed files out of the upload folder—failed files will just stack up.

The error occurs when sending the files to the HTTP server, so you look into the log files and quickly determine that Camel can’t connect to the remote HTTP server due to network issues. Your boss decides that the application should retry uploading the files if there’s an error, so the files won’t have to wait for the next hourly upload.

To implement this, you can configure the error handler to redeliver up to 5 times with 10-second delays:

errorHandler(defaultErrorHandler()
.maximumRedeliveries(5).redeliveryDelay(10000));

Configuring redelivery can hardly get any simpler than that. But let’s take a closer look at how to use redelivery with Camel.

5.3.2. Using redelivery

The first three error handlers in table 5.1 all support redelivery. This is implemented in the RedeliveryErrorHandler class, which they extend. The RedeliveryErrorHandler must then know whether or not to attempt redelivery; this is what the redelivery policy is for.

A redelivery policy defines how and whether redelivery should be attempted. Table 5.3 outlines the options supported by the redelivery policy and what the default settings are.

Table 5.3. Options provided in Camel for configuring redelivery

Option

Type

Default

Description

MaximumRedeliveries int 0 Maximum number of redelivery attempts allowed. 0 is used to disable redelivery, and -1 will attempt redelivery forever until it succeeds.
RedeliveryDelay long 1000 Fixed delay in milliseconds between each redelivery attempt.
MaximumRedeliveryDelay long 60000 An upper bound in milliseconds for redelivery delay. This is used when you specify non-fixed delays, such as exponential backoff, to avoid the delay growing too large.
AsyncDelayedRedelivery boolean false Dictates whether or not Camel should use asynchronous delayed redelivery. When a redelivery is scheduled to be redelivered in the future, Camel would normally have to block the current thread until it’s time for redelivery. By enabling this option, you let Camel use a scheduler so that an asynchronous thread will perform the redelivery. This ensures that no thread is blocked while waiting for redelivery.
BackOffMultiplier double 2.0 Exponential backoff multiplier used to multiply each consequent delay. RedeliveryDelay is the starting delay. Exponential backoff 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.
DelayPattern String - A pattern to use for calculating the delay. The pattern allows you to specify fixed delays for interval groups.
      For example, the pattern "0:1000; 5:5000;10:30000" will use a 1 second delay for attempts 0 to 4, 5 seconds for attempts 5 to 9, and 30 seconds for subsequent attempts.
RetryAttemptedLogLevel LoggingLevel DEBUG Log level used when a redelivery attempt is performed.
RetriesExhaustedLogLevel LoggingLevel ERROR Log level used when all redelivery attempts have failed.
LogStackTrace boolean true Specifies whether or not stacktraces should be logged when all redelivery attempts have failed.
LogRetryStackTrace boolean false Specifies whether or not stacktraces should be logged when a delivery has failed.
LogRetryAttempted boolean true Specifies whether or not redelivery attempts should be logged.
LogExhausted boolean true Specifies whether or not the exhaustion of redelivery attempts (when all redelivery attempts have failed) should be logged.
LogHandled boolean false Specifies whether or not handled exceptions should be logged.

In the Java DSL, Camel has fluent builder methods for configuring the redelivery policy on the error handler. For instance, if you want to redeliver up to five times, use exponential backoff, and have Camel log at WARN level when it attempts a redelivery, you could use this code:

errorHandler(defaultErrorHandler()
.maximumRedeliveries(5)
.backOffMultiplier(2)
.retryAttemptedLogLevel(LoggingLevel.WARN));

Configuring this in Spring XML is done as follows:

<errorHandler id="myErrorHandler" type="DefaultErrorHandler"
<redeliveryPolicy maximumRedeliveries="5"
retryAttemptedLogLevel="WARN"
backOffMultiplier="2"
useExponentialBackOff="true"/>
</errorHandler>

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.

We’ve now established that Camel uses the information from the redelivery policy to determine whether and how to do redeliveries. But what happens inside Camel? As you’ll recall from figure 5.3, Camel includes a Channel between every processing step in a route path, and there is functionality in these Channels, such as error handlers. The error handler detects every exception that occurs and acts on it, deciding what to do, such as redeliver or give up.

Now that you know a lot about the DefaultErrorHandler, it’s time to try a little example.

Using the Defaulterrorhandler with Redelivery

In the source code for the book, you’ll see an example in the chapter5/errorhandler directory. The example uses the following route configuration:

This configuration first defines a context-scoped error handler that will attempt at most two redeliveries using a 1-second delay. When it attempts the redelivery, it will log this at the WARN level (as you’ll see in a few seconds). The example is constructed to fail when the message reaches the enrich method .

You can run this example using the following Maven goal from the chapter5/errorhandler directory:

mvn test -Dtest=DefaultErrorHandlerTest

When running the example, you’ll see the following log entries outputted on the console. Notice how Camel logs the redelivery attempts:

2009-12-16 14:28:16,959 [a://queue.inbox] WARN  DefaultErrorHandler
- Failed delivery for exchangeId: 64bc46c0-5cb0-4a78-a4a8-9159f5273601.
On delivery attempt: 0 caught: camelinaction.OrderException: ActiveMQ in
Action is out of stock
2009-12-16 14:28:17,960 [a://queue.inbox] WARN DefaultErrorHandler
- Failed delivery for exchangeId: 64bc46c0-5cb0-4a78-a4a8-9159f5273601.
On delivery attempt: 1 caught: camelinaction.OrderException: ActiveMQ in
Action is out of stock

These log entries show that Camel failed to deliver a message, which means the entry is logged after the attempt is made. On delivery attempt: 0 identifies the first attempt; attempt 1 is the first redelivery attempt. Camel also logs the exchangeId (which you can use to correlate messages) and the exception that caused the problem (without the stacktrace, by default).

When Camel performs a redelivery attempt it does this at the point of origin. In the preceding example the error occurred when invoking the enrich method , which means Camel will redeliver by retrying the .beanRef("orderService", "enrich") step in the route.

After all redelivery attempts have failed, we say it’s exhausted, and Camel logs this at the ERROR level by default. (You can customize this with the options listed in table 5.3.) When the redelivery attempts are exhausted, the log entry is similar to the previous ones, but Camel explains that it’s exhausted after three attempts:

2009-12-16 14:28:18,961 [a://queue.inbox] ERROR DefaultErrorHandler
- Failed delivery for exchangeId: 64bc46c0-5cb0-4a78-a4a8-9159f5273601.
Exhausted after delivery attempt: 3 caught:
camelinaction.OrderException: ActiveMQ in Action is out of stock

 

Tip

The default error handler has many options, which are listed in table 5.3. We encourage you to try loading this example into your IDE and playing with it. Change the settings on the error handler and see what happens.

 

The preceding log output identifies the number of redelivery attempts, but how does Camel know this? Camel stores this information on the Exchange. Table 5.4 reveals where this information is stored.

Table 5.4. Headers on the Exchange related to error handling

Header

Type

Description

Exchange.REDELIVERY_COUNTER int The current redelivery attempt.
Exchange.REDELIVERED boolean Whether this Exchange is being redelivered.
Exchange.REDELIVERY_EXHAUSTED boolean Whether this Exchange has attempted (exhausted) all redeliveries and has still failed.

The information in table 5.4 is only available when Camel performs a redelivery; these headers are absent on the regular first attempt. It’s only when a redelivery is triggered that these headers are set on the exchange.

Using Asynchronous Delayed Redelivery

In the previous example, the error handler was configured to use delayed redelivery with a 1-second delay between attempts. When a redelivery is to be conducted, Camel will wait for 1 second before carrying out the redelivery.

If you look at the console output, you can see the redelivery log entries are 1 second apart, and it’s the same thread processing the attempts; this can be identified by the [a://queue.inbox] being logged. This is known as synchronous delayed redelivery. There will also be situations where you want to use asynchronous delayed redelivery. So what does that mean?

Suppose two orders are sent to the seda:queue:inbox endpoint. The consumer will pick up the first order from the queue and process it. If it fails, it’s scheduled for redelivery. In the synchronous case, the consumer thread is blocked while waiting to carry out the redelivery. This means the second order on the queue can only be processed when the first order has been completed.

This isn’t the case in asynchronous mode. Instead of the consumer thread being blocked, it will break out and be able to pick up the second order from the queue and continue processing it. This helps achieve higher scalability because threads aren’t blocked and doing nothing. Instead the threads are being put to use servicing new requests.

 

Tip

We’ll cover the threading model in chapter 10, which will explain how Camel can schedule redeliveries for the future to be processed by other threads. The Delayer and Throttler EIPs have similar asynchronous delayed modes, which you can leverage by enabling the asyncDelayed option.

 

The source code for the book contains an example that illustrates the difference between synchronous and asynchronous delayed redelivery, in the chapter5/errorhandler directory. You can try it using the following Maven goal:

mvn test -Dtest=SyncVSAsyncDelayedRedeliveryTest

The example contains two methods: one for the synchronous mode and another for the asynchronous.

The console output for the synchronous mode should be displayed in the following order:

[a://queue.inbox] INFO - Received input amount=1,name=ActiveMQ in Action
[a://queue.inbox] WARN - Failed delivery for exchangeId: xxxx
[a://queue.inbox] WARN - Failed delivery for exchangeId: xxxx
[a://queue.inbox] WARN - Failed delivery for exchangeId: xxxx
[a://queue.inbox] INFO - Received input amount=1,name=Camel in Action
[a://queue.inbox] INFO - Received order amount=1,name=Camel in
Action,id=123,status=OK

Compare that with the following output from the asynchronous mode:

[a://queue.inbox] INFO - Received input amount=1,name=ActiveMQ in Action
[a://queue.inbox] WARN - Failed delivery for exchangeId: xxxx
[a://queue.inbox] INFO - Received input amount=1,name=Camel in Action
[a://queue.inbox] INFO - Received order amount=1,name=Camel in
Action,id=123,status=OK
[rRedeliveryTask] WARN - Failed delivery for exchangeId: xxxx
[rRedeliveryTask] WARN - Failed delivery for exchangeId: xxxx

Notice how the Camel in Action order is processed immediately when the first order fails and is scheduled for redelivery. Also pay attention to the thread name that executes the redelivery, identified by [rRedeliveryTask] being logged. As you can see, it’s not the consumer anymore; its a redelivery task.

5.3.3. Error handlers and scopes

Scopes can be used to define error handlers at different levels. Camel supports two scopes: a context scope and a route scope.

Camel allows you to define a global context-scoped error handler that’s used by default, and, if needed, you can also configure a route-scoped error handler that applies only for a particular route. This is illustrated in listing 5.1.

Listing 5.1. Using two error handlers at different scopes

Listing 5.1 is an improvement over the previous error-handling example. The default error handler is configured as in the previous example , but you have a new route that picks up files, processes them, and sends them to the second route. This first route will use the default error handler because it doesn’t have a route-scoped error handler configured, but the second route has a route-scoped error handler . It’s a Dead Letter Channel that will send failed messages to a log. Notice that it has different options configured than the former error handler.

The source code for the book includes this example, which you can run using the following Maven goal from the chapter5/errorhandler directory:

mvn test -Dtest=RouteScopeTest

This example should fail for some messages when the enrich method is invoked. This demonstrates how the route-scoped error handler is used as error handler.

The most interesting part of this test class is the testOrderActiveMQ method, which will fail in the second route and therefore show the Dead Letter Channel in action. There are a couple of things to notice about this, such as the exponential backoff, which causes Camel to double the delay between redelivery attempts, starting with 250 milliseconds and ending with 4 seconds.

The following snippets show what happens at the end when the error handler is exhausted.

2009-12-16 17:03:44,534 [a://queue.inbox] INFO  DeadLetterChannel
- Failed delivery for exchangeId: e80ed4ba-12b3-472c-9b35-31beed4ff51b.
On delivery attempt: 5 caught: camelinaction.OrderException: ActiveMQ in
Action is out of stock
2009-12-16 17:03:44,541 [a://queue.inbox] INFO DLC
- Exchange[BodyType:String, Body:amount=1,name=ActiveMQ in
Action,id=123]
2009-12-16 17:03:44,542 [a://queue.inbox] ERROR DeadLetterChannel
- Failed delivery for exchangeId: e80ed4ba-12b3-472c-9b35-31beed4ff51b.
Exhausted after delivery attempt: 6 caught:
camelinaction.OrderException: ActiveMQ in Action is out of stock.
Processed by failure processor: sendTo(Endpoint[log://DLC])

As you can see, the Dead Letter Channel moves the message to its dead letter queue, which is the log://DLC endpoint. After this, Camel also logs an ERROR line indicating that this move was performed.

We encourage you to try this example and adjust the configuration settings on the error handlers to see what happens.

So far, the error-handling examples we’ve looked at have used the Java DSL. Let’s take a look at configuring error handling with Spring XML.

Using Error Handling with Spring XML

Let’s revise the example in listing 5.1 to use Spring XML. Here’s how that’s done.

Listing 5.2. Using error handling with Spring XML

To use a context-scoped error handler in Spring XML, you must configure it using an errorHandlerRef attribute on the camelContext tag. The errorHandlerRef refers to an <errorHandler>, which in this case is the default error handler with id "defaultEH" . There’s another error handler, a DeadLetterChannel error handler , that is used at route scope in the second route .

As you can see, the differences between the Java DSL and Spring XML mostly result from using the errorHandlerRef attribute to reference the error handlers in Spring XML, whereas Java DSL can have route-scoped error handlers within the routes.

You can try this example by running the following Maven goal from the chapter5/errorhandler directory:

mvn test -Dtest=SpringRouteScopeTest

The Spring XML file is located in the src/test/resources/camelinaction directory.

This concludes our discussion of scopes and redelivery. We’ll now look at how you can use Camel error handlers to handle faults.

5.3.4. Handling faults

In the introduction to section 5.2, we mentioned 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. Suppose a Camel route invokes a remote web service that returns a fault message, and you want this fault message to be treated like an exception and moved to a dead letter queue.

We’ve implemented this scenario as a unit test, simulating the remote web service using a bean:

errorHandler(deadLetterChannel("mock:dead"));

from("seda:queue.inbox")
.beanRef("orderService", "toSoap")
.to("mock:queue.order");

Now, imagine that the orderService bean returns the following SOAP fault:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:Envelope xmlns:ns2="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns3="http://www.w3.org/2003/05/soap-envelope">
<ns2:Body>
<ns2:Fault>
<faultcode>ns3:Receiver</faultcode>
<faultstring>ActiveMQ in Action is out of stock</faultstring>
</ns2:Fault>
</ns2:Body>
</ns2:Envelope>

Under normal situations, the Camel error handler won’t react when the SOAP fault occurs. To make it do so, you have to instruct Camel by enabling fault handling.

To enable fault handling on the CamelContext (context scope), you simply do this:

getContext().setHandleFault(true);

To enable it on a per route basis (route scope), do this:

from("seda:queue.inbox").handleFault()
.beanRef("orderService", "toSoap")
.to("mock:queue.order");

Once fault handling is enabled, the Camel errors handlers will recognize the SOAP faults and react. Under the hood, the SOAP fault is converted into an Exception with the help of an interceptor.

You can enable fault handling in Spring XML as follows:

<route handleFault="true">
<from uri="seda:queue.inbox"/>
<bean ref="orderService" method="toSoap"/>
<to uri="mock:queue.order"/>
</route>

The source code for the book contains this example in the chapter5/errorhandler directory, which you can try using the following Maven goals:

mvn test -Dtest=HandleFaultTest
mvn test -Dtest=SpringHandleFaultTest

 

Tip

You can enable fault handling to let Camel error handlers react to faults returned from components such as CXF, SOAP, JBI, or NMR.

 

We’ll continue in the next section to look at the other two major features that error handlers provide, as listed in table 5.2: exception policies and error handling.

5.4. 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.

 

Note

In Camel, exception policies are specified with the onException method in the route, so we’ll use the term onException interchangeably with “exception policy.”

 

We’ll cover exception policies piece by piece, looking at how they catch exceptions, how they works with redelivery, and how they handle exceptions. Then we’ll take a look at custom error handling and put it all to work in an example.

5.4.1. Understanding how onException catches exceptions

We’ll start by looking at how Camel inspects the exception hierarchy to determine how to handle the error. This will give you a better understanding of how you can use onException to your advantage.

Imagine you have this exception hierarchy being thrown:

org.apache.camel.RuntimeCamelException (wrapper by Camel)
+ com.mycompany.OrderFailedException
+ java.net.ConnectException

The real cause is a ConnectException, but it’s wrapped in an OrderFailedException and yet again in a RuntimeCamelException.

Camel will traverse the hierarchy from the bottom up to the root searching for an onException that matches the exception. In this case, Camel will start with java.net.ConnectException, move on to com.mycompany.OrderFailedException, and finally reach RuntimeCamelException. For each of those three exceptions, Camel will compare the exception to the defined onExceptions to select the best matching onException policy. If no suitable policy can be found, Camel relies on the configured error handler settings. We’ll drill down and look at how the matching works, but for now you can think of this as Camel doing a big instanceof check against the exceptions in the hierarchies, following the order in which the onExceptions were defined.

Suppose you have a route with the following onException:

onException(OrderFailedException.class).maximumRedeliveries(3);

The aforementioned ConnectException is being thrown, and the Camel error handler is trying to handle this exception. Because you have an exception policy defined, it will check whether the policy matches the thrown exception or not. The matching is done as follows:

  1. Camel starts with the java.net.ConnectException and compares it to onException(OrderFailedException.class). Camel checks whether the two exceptions are exactly the same type, and in this case they’re not—ConnectionException and OrderFailedException aren’t the same type.
  2. Camel checks whether ConnectException is a subclass of OrderFailedException, and this isn’t true either. So far, Camel has not found a match.
  3. Camel moves up the exception hierarchy and compares again with OrderFailedException. This time there is an exact match, because they’re both of the type OrderFailedException.

No more matching takes place—Camel got an exact match, and the exception policy will be used.

When an exception policy has been selected, its configured policy will be used by the error handler. In this example, the policy defines the maximum redeliveries to be 3, so the error handler will attempt at most 3 redeliveries when this kind of exception is thrown.

Any value configured on the exception policy will override options configured on the error handler. For example, suppose the error handler had the maximumRedeliveries option configured as 5. Because the onException has the same option configured, its value of 3 will be used instead.

 

Note

The book’s source code has an example that demonstrates what you’ve just learned. Take a look at the OnExceptionTest class in chapter5/onexception. It has multiple test methods, each showing a scenario of how onException works.

 

Let’s make the example a bit more interesting and add a second onException definition:

onException(OrderFailedException.class).maximumRedeliveries(3);
onException(ConnectException.class).maximumRedeliveries(10);

If the same exception hierarchy is thrown as in the previous example, Camel would select the second onException because it directly matches the ConnectionException. This allows you to define different strategies for different kinds of exceptions. In this example, it is configured to use more redelivery attempts for connection exceptions than for order failures.

 

Tip

This example demonstrates how onException can influence the redelivery polices the error handler uses. If an error handler was configured to perform only 2 redelivery attempts, the preceding onException would overload this with 10 redelivery attempts in the case of connection exceptions.

 

But what if there are no direct matches? Let’s look at another example. This time, imagine that a java.io.IOException exception was thrown. Camel will do its matching, and because OrderFailedException isn’t a direct match, and IOException isn’t a subclass of it, it’s out of the game. The same applies for the ConnectException. In this case, there are no onException definitions that match, and Camel will fall back to using the configuration of the current error handler.

You can see this in action by running the following Maven goal from chapter 5/ onexception directory:

mvn test -Dtest=OnExceptionFallbackTest
onException and Gap Detection

Can Camel do better if there isn’t a direct hit? Yes, it can, because Camel uses a gap-detection mechanism that calculates the gaps between a thrown exception and the onExceptions and then selects the onException with the lowest gap as the winner. This may sound confusing, so let’s look at an example.

Suppose you have these three onException definitions, each having a different redelivery policy:

onException(ConnectException.class)
.maximumRedeliveries(5);
onException(IOException.class)
.maximumRedeliveries(3).redeliveryDelay(1000);
onException(Exception.class)
.maximumRedeliveries(1).redeliveryDelay(5000);

And imagine this exception is thrown:

org.apache.camel.OrderFailedException
+ java.io.FileNotFoundException

Which of those three onExceptions would be selected?

Camel starts with the java.io.FileNotFoundException and compares it to the onException definitions. Because there are no direct matches, Camel uses gap detection. In this example, only onException(IOException.class) and onException(Exception.class) partly match, because java.io.FileNotFoundException is a subclass of java.io.IOException and java.lang.Exception.

Here’s the exception hierarchy for FileNotFoundException:

java.lang.Exception
+ java.io.IOException
+ java.io.FileNotFoundException

Looking at this exception hierarchy, you can see that java.io.FileNotFoundException is a direct subclass of java.io.Exception, so the gap is computed as 1. The gap between java.lang.Exception and java.io.FileNotFoundException is 2. At this point, the best candidate has a gap of 1.

Camel will then go the same process with the next exception from the thrown exception hierarchy, which is OrderFailedException. This time, it’s only the onException(Exception.class) that partly matches, and the gap between OrderFailedException and Exception is also 1:

java.lang.Exception
+ OrderNotFoundException

So what now? You have two gaps, both calculated as 1. In the case of a tie, Camel will always pick the first match, because the cause exception is most likely the last in the hierarchy. In this case, it’s a FileNotFoundException, so the winner will be onException(IOException.class).

This example is provided in the source code for the book in the chapter5/onexception directory. You can try it using the following Maven goal:

mvn test -Dtest=OnExceptionGapTest

Gap detection allows you to define coarse-grained policies and also to have a few fine-grained policies that overrule the coarse-grained ones. Does this sound familiar? Yes, it’s related to the scoping that we covered in section 5.3.

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(XPathException.class, TransformerException.class)
.to("log:xml?level=WARN");

onException(IOException.class, SQLException.class, JMSException.class)
.maximumRedeliveries(5).redeliveryDelay(3000);

Here’s the same example using Spring XML:

<camelContext xmlns="http://camel.apache.org/schema/spring">
<onException>
<exception>javax.xml.xpath.XPathException</exception>
<exception>javax.xml.transform.TransformerException</exception>
<to uri="log:xml?level=WARN"/>
</onException>

<onException>
<exception>java.io.IOException</exception>
<exception>java.sql.SQLException</exception>
<exception>javax.jms.JmsException</exception>
<redeliverPolicy maximumRedeliveries="5" redeliveryDelay="3000"/>
</onException>
</camelContext>

Our next topic is how onException works with redelivery. Even though we’ve touched on this already in our examples, we’ll go into the details in the next section.

5.4.2. Understanding how onException works with redelivery

onException works with redeliveries, but there are a couple of things you need to be aware of that might not be immediately obvious.

Suppose you have the following route:

from("jetty:http://0.0.0.0/orderservice")
.to("mina:tcp://erp.rider.com:4444?textline=true")
.beanRef("orderBean", "prepareReply");

You use the Camel Jetty component to expose an HTTP service where statuses of pending orders can be queried. The order status information is retrieved from a remote ERP system by the MINA component using low-level socket communication. You’ve learned how to configure this on the error handler itself, but it’s also possible to configure this on the onException.

Suppose you want Camel to retry invoking the external TCP service, in case there has been an IO-related error, such as a lost network connection. To do this, you can simply add the onException and configure the redelivery policy as you like. In the following example, the redelivery tries at most 5 times:

onException(IOException.class).maximumRedeliveries(5);

You’ve already learned that onException(IOException.class) will catch those IO-related exceptions and act accordingly. But what about the delay between redeliveries?

In this example, the delay will be 1 second. Camel will use the default redelivery policy settings outlined in table 5.3 and then override those values with values defined in the onException. Because the delay was not overridden in the onException, the default value of 1 second is used.

 

Tip

When you configure redelivery policies, 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.

 

Now let’s make it a bit more complicated:

errorHandler(defaultErrorHandler().maximumRedeliveries(3).delay(3000));

onException(IOException.class).maximumRedeliveries(5);

from("jetty:http://0.0.0.0/orderservice")
.to("mina:tcp://erp.rider.com:4444?textline=true")
.beanRef("orderBean", "prepareReply");

What would the redelivery delay be if an IOException were thrown? Yes, it’s 3 seconds, because onException will fall back and use the redelivery policies defined by the error handler, and its value is configured as delay(3000).

Now let’s remove the maximumRedeliveries(5) option from the onException, so it’s defined as onException(IOException.class):

errorHandler(defaultErrorHandler().maximumRedeliveries(3).delay(3000));

onException(IOException.class);

from("jetty:http://0.0.0.0/orderservice")
.to("mina:tcp://erp.rider.com:4444?textline=true")
.beanRef("orderBean", "prepareReply");

What would the redelivery delay be now, if an IOException were thrown? I am sure you’ll say the answer is 3—the value defined on the error handler. In this case, though, the answer is 0. Camel won’t attempt to do any redelivery because any onException will override the maximumRedeliveries to 0 by default (redelivery is disabled by default) unless you explicitly set the maximumRedeliveries option.

The reason why Camel implements this behavior is our next topic: using onException to handle exceptions.

5.4.3. Understanding how onException can handle exceptions

Suppose you have a complex route that processes a message in multiple steps. Each step does some work on the message, but any step can throw an exception to indicate that the message can’t be processed and that it should be discarded. This is where handling exceptions with onException comes into the game.

Handling an exception with onException is similar to exception handling in Java itself. You can think of it as being like using a try ... catch block.

This is best illustrated with an example. Imagine you need to implement an ERP server-side service that serves order statuses. This is the ERP service you called from the previous section:

This snippet of pseudocode involves multiple steps in generating the response. If something goes wrong, you catch the exception and return a failure response .

We call this pseudocode because it shows your intention but the code won’t compile. This is because the Java DSL uses the fluent builder syntax, where method calls are stacked together to define the route. The regular try ... catch mechanism in Java works at runtime to catch exceptions that are thrown when the configure() method is executed, but in this case the configure() method is only invoked once, when Camel is started (when it initializes and builds up the route path to use at runtime).

Don’t despair. Camel has a counterpart to the classic try ... catch ... finally block in its DSL: doTry ... doCatch ... doFinally.

Using doTry, doCatch, and Dofinally

Listing 5.3 shows how you can make the code compile and work at runtime as you would expect with a try ... catch block.

Listing 5.3. Using doTry ... doCatch with Camel routing
public void configure() {
from("mina:tcp://0.0.0.0:4444?textline=true")
.doTry()
.process(new ValidateOrderId())
.to("jms:queue:order.status")
.process(new GenerateResponse());
.doCatch(JmsException.class)
.process(new GenerateFailureResponse())
.end();
}

The doTry ... doCatch block was 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.

Using onException to Handle Exceptions

The doTry ... doCatch block has one limitation—it’s only route scoped. The blocks only work in the route in which they’re defined. OnException, on the other hand, works in both context and route scopes, so you can try revising listing 5.3 using onException. This is illustrated in listing 5.4.

Listing 5.4. Using onException in context scope

A difference between doCatch and onException is that doCatch will handle the exception, whereas onException will, by default, not handle it. That’s why you use handled(true) to instruct Camel to handle this exception. As a result, when a JmsException is thrown, the application acts as if the exception were caught in a catch block using the regular Java try ... catch mechanism.

In listing 5.4, you should also notice how the concerns are separated and the normal route path is laid out nicely and simply; it isn’t mixed up with the exception handling.

Imagine that a message arrives on the TCP endpoint, and the Camel application routes the message. The message passes the validate processor and is about to be sent to the JMS queue, but this operation fails and a JmsException is thrown. Figure 5.5 is a sequence diagram showing the steps that take place inside Camel in such a situation. It shows how onException is triggered to handle the exception.

Figure 5.5. Sequence diagram of a message being routed and a JmsException being thrown from the JmsProducer, which is handled by the onException. OnException generates a failure that is to be returned to the caller.

Figure 5.5 shows how the JmsProducer throws the JmsException to the Channel, which is where the error handler lives. The route has an OnException defined that reacts when a JmsException is thrown, and it processes the message. The GenerateFailureResponse processor generates a custom failure message that is supposed to be returned to the caller. Because the OnException was 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.

 

Note

OnException doesn’t handle exceptions by default, so listing 5.4 uses handled(true) to indicate that onException should handle the exception. This is important to remember, because it must be specified when you want to handle the exception. Handling an exception will not continue routing from the point where the exception was thrown. Camel will break out of the route and continue routing on the onException. If you want to ignore the exception and continue routing, you must use continued(true), which will be discussed in section 5.4.5.

 

Before we move on, let’s take a minute to look at the example from listing 5.4 revised to use Spring XML. The syntax is a bit different, as you can see:

Listing 5.5. Spring XML revision of listing 5.4

Notice how onException is set up—you must define the exceptions in the exception tag. Also, handled(true) is a bit longer because you must enclose it in the <constant> expression. There are no other noteworthy differences in the rest of the route.

Listing 5.5 uses a custom processor to generate a failure response . Let’s take a closer look at that.

5.4.4. Custom exception handling

Suppose you want to return a custom failure message, as in listing 5.5, that indicates not only what the problem was but that also includes details from the current Camel Message. How can you do that?

Listing 5.5 laid out how to do this using onException. Listing 5.6 shows how the failure Processor could be implemented.

Listing 5.6. Using a processor to create a failure response to be returned to the caller

First, you grab the information you need: the message body and the exception . It may seem a bit odd that you get the exception as a property and not using exchange.getException(). You do that because you’ve marked onException to handle the exception; this was done at in listing 5.5. When you do that, Camel moves the exception from the Exchange to the Exchange.EXCEPTION_CAUGHT property. The rest of the processor builds the custom failure message that’s to be returned to the caller.

You may wonder whether there are other properties Camel sets during error handling, and there are. They’re listed in table 5.5. But from an end-user perspective, it’s only the first two properties in table 5.5 that matter. The other two properties are used internally by Camel in its error-handling and routing engine.

Table 5.5. Properties on the Exchange related to error handling

Property

Type

Description

Exchange.EXCEPTION_CAUGHT Exception The exception that was caught.
Exchange.FAILURE_ENDPOINT String The URL of the endpoint that failed if a failure occurred when sending to an endpoint. If the failure did not occur while sending to an endpoint, this property is null.
Exchange.ERRORHANDLER_HANDLED Boolean Whether or not the error handler handled the exception.
Exchange.FAILURE_HANDLED Boolean Whether or not onException handled the exception. Or true if the Exchange was moved to a dead letter queue.

One example of when the FAILURE_ENDPOINT property comes in handy is when you route messages through the Recipient List EIP, which sends a copy of the message to a dynamic number of endpoints. Without this information, you wouldn’t know precisely which of those endpoints failed.

It’s worth noting that in listing 5.6 you use a Camel Processor, which forces you to depend on the Camel API. You can use a bean instead, as follows:

As you can see, you can use Camel’s parameter binding to declare the parameter types you want to use. The first parameter is the message body, and the second is the exception.

There will be situations where you’ll want to simply ignore the exception and continue routing.

5.4.5. Ignoring exceptions

In section 5.4.3 we learned about how 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, laid out in listing 5.4. Listing 5.7 shows how we can do this.

Listing 5.7. Using continued to ignore ValidationExceptions

As you can see, all you have to do is add another onException that leverages continued(true) .

 

Note

You can’t use both handled and continued on the same onException; continued automatically implies handled.

 

Now imagine that a message once again arrives on the TCP endpoint, and the Camel application routes the message. But this time the validate processor throws a ValidationException. This situation is illustrated in figure 5.6.

Figure 5.6. Sequence diagram of a message being routed and a ValidationException being thrown from the ValidateProcessor. The exception is handled and continued by the onException policy, causing the message to continue being routed as if the exception were not thrown.

When the ValidateProcessor throws the ValidationException, it’s propagated back to the Channel, which lets the error handler kick in. The route has an onException defined that instructs the Channel to continue routing the message—continued(true).

When the message arrives at the next Channel, it’s as if the exception were not thrown. This is much different from what you saw in section 5.4.3 when using handled(true), which causes the processing to break out and not continue routing.

You’ve learned a bunch of new stuff, so let’s continue with the error handler example and put your knowledge into practice.

5.4.6. Implementing an error handler solution

Suppose your boss brings you a new problem. This time, the remote HTTP server used for uploading files is unreliable, and he wants you to implement a secondary failover to transfer the files by FTP to a remote FTP server.

You have been studying Camel in Action, and you’ve learned that Camel has extensive support for error handling and that you could leverage onException to provide this kind of feature. With great confidence, you fire up the editor and alter the route as shown in listing 5.8.

Listing 5.8. Route using error handling with failover to FTP

This listing adds an onException to the route, telling Camel that in the case of an IOException, it should try redelivering up to 3 times using a 10-second delay. If there is 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. The onException is just another route, and Camel will continue on this route instead of the original route.

 

Note

In listing 5.8, it’s only when onException is exhausted that it will reroute the message to the FTP endpoint . The onException has been configured to redeliver up till 3 times before giving up and being exhausted.

 

The book’s source code contains this example in the chapter5/usecase directory, and you can try it out yourself. The example contains a server and a client that you can start using Maven:

mvn exec:java -PServer
mvn exec:java -PClient

Both the server and client output instructions on the console about what to do next, such as copying a file to the target/rider folder to get the ball rolling.

Before we finish up this chapter, we must take a look at a few more error-handling features. They’re used rarely, but they provide power in situations where you need more fine-grained control.

5.5. Other error-handling features

We’ll end this chapter by looking at some of the other features Camel provides for error handling:

  • onWhen—Allows you to dictate when an exception policy is in use
  • onRedeliver—Allows you to execute some code before the message is redelivered
  • retryWhile—Allows you, at runtime, to determine whether or not to continue redelivery or to give up

We’ll look at each in turn.

5.5.1. Using onWhen

The onWhen predicate filter allows more fine-grained control over when an onException should be triggered.

Suppose a new problem has emerged with your application in listing 5.8. This time the HTTP service rejects the data and returns an HTTP 500 response with the constant text “ILLEGAL DATA”. Your boss wants you to handle this by moving the file to a special folder where it can be manually inspected to see why it was rejected.

First, you need to determine when an HTTP error 500 occurs and whether it contains the text “ILLEGAL DATA”. You decide to create a Java method that can test this, as shown in listing 5.9.

Listing 5.9. A helper to determine whether an HTTP error 500 occurred

When the HTTP operation isn’t successful, the Camel HTTP component will throw an org.apache.camel.component.http.HttpOperationFailedException exception, containing information why it failed. The getStatusCode() method on HttpOperationFailedException , returns the HTTP status code. This allows you to determine if it’s an HTTP error code 500 with the “ILLEGAL DATA” body text.

Next, you need to use the utility class from listing 5.9 in your existing route from listing 5.8. But first you add the onException to handle the HttpOperationFailedException and detour the message to the illegal folder:

onException(HttpOperationFailedException.class)
.handled(true)
.to("file:/rider/files/illegal");

Now, whenever an HttpOperationFailedException is thrown, Camel moves the message to the illegal folder.

It would be better if you had more fine-grained control over when this onException triggers. How could you incorporate your code from listing 5.9 with the onException?

I am sure you have guessed where we’re going—yes, you can use the onWhen predicate. All you need to do is insert the onWhen into the onException, as shown here:

onException(HttpOperationFailedException.class)
.onWhen(bean(MyHttpUtil.class, "isIllegalData"))
.handled(true)
.to("file:/acme/files/illegal");

Camel adapts to your POJO classes and uses them as is, thanks to the power of Camel’s parameter binding, which we covered in the previous chapter. This is a powerful way to develop your application without being tied to the Camel API. onWhen is a general function that also exists in other Camel features, such as interceptors and onCompletion, so you can use this technique in various situations.

Next, let’s look at onRedeliver, which allows fine-grained control when a redelivery is about to occur.

5.5.2. Using onRedeliver

The purpose of onRedeliver is to allow some code to be executed before a redelivery is performed. This gives you the power to do custom processing on the Exchange before Camel makes a redelivery attempt. You can, for instance, use it to add custom headers to indicate to the receiver that this is a redelivery attempt. OnRedeliver uses an org.apache.camel.Processor, in which you implement the code to be executed.

OnRedeliver can be configured on the error handler, on onException, or on both, as follows:

errorHandler(defaultErrorHandler()
.maximumRedeliveries(3)
.onRedeliver(new MyOnRedeliveryProcessor());

onException(IOException.class)
.maximumRedeliveries(5)
.onRedeliver(new MyOtherOnRedeliveryProcessor());

OnRedeliver is also scoped, so if an onRedeliver is set on an onException, it overrules any onRedeliver set on the error handler.

In Spring DSL, onRedeliver is configured as a reference to a spring bean, as follows:

<onException onRedeliveryRef="myOtherRedelivery">
<exception>java.io.IOException</exception>
</onException>

<bean id="myOtherRedelivery"
class="com.mycompany.MyOtherOnRedeliveryProceossor"/>

Finally, let’s look at one last feature: RetryWhile.

5.5.3. Using retryWhile

RetryWhile is used when you want fine-grained control over the number of redelivery attempts. It’s also a predicate that’s scoped, so you can define it on the error handler or on onException.

You can use retryWhile to implement your own generic retry ruleset that determines how long it should retry. Listing 5.10 shows some skeleton code demonstrating how this can be done.

Listing 5.10. Skeleton code to illustrate principle of using retryWhile
public class MyRetryRuleset {

public boolean shouldRetry(
@Header(Exchange.REDELIVERY_COUNTER) Integer counter,
Exception causedBy) {
...
}

Using your own MyRetryRuleset class, you can implement your own logic determining whether it should continue retrying or not. If the method returns true, a redelivery attempt is conducted; if it returns false, it give up.

To use your ruleset, you configure retryWhile on the onException as follows:

onException(IOException.class).retryWhile(bean(MyRetryRuletset.class));

In Spring XML you configure retryWhile as shown:

<onException>
<exception>java.io.IOException</exception>
<retryWhile><method ref="myRetryRuleset"/></retryWhile>
</onException>

<bean id="myRetryRuleset" class="com.mycompany.MyRetryRuleset"/>

That gives you fine-grained control over the number of redelivery attempts performed by Camel.

That’s it! We’ve now covered all the features Camel provides for fine-grained control over error handling.

5.6. Summary and best practices

In this chapter, you saw how recoverable and irrecoverable errors are represented in Camel. We also looked at all the provided error handlers, focusing on the most important of them. You saw how Camel can control how exceptions are dealt with, using redelivery policies to set the scene and exception policies to handle specific exceptions differently. Finally, we looked at what Camel has to offer when it comes to fine-grained control over error handling, putting you in control of error handling in Camel.

Let’s revisit some of the key ideas from this chapter, which you can take away and apply to your own Camel applications:

  • Error handling is hard. Realize from the beginning that the unexpected can happen and that dealing with errors is hard. The challenge keeps rising when businesses have more and more of their IT portfolio integrated and operate it 24/7/365.
  • Error handling isn’t an afterthought. When IT systems are being integrated, they exchange data according to agreed-upon protocols. Those protocols should also specify how errors will be dealt with.
  • Separate routing logic from error handling. Camel allows you to separate routing logic from error-handling logic. This avoids cluttering up your logic, which otherwise could become harder to maintain. Use Camel features such as error handlers, onException, and doTry ... doCatch.
  • Try to recover. Some errors are recoverable, such as connection errors. You should apply strategies to recover from these errors.
  • Use asynchronous delayed redelivery. If the order of messages processed from consumers doesn’t matter, leverage asynchronous redelivery to achieve higher scalability.
  • Handle fault messages. If you use components such as JBI, CXF, or SOAP, which may return fault messages, you can enable fault handling in Camel to let the error handlers react to those faults.
  • Use monitoring tooling. Use tooling to monitor your Camel applications so it can react and alert personnel if severe errors occur. Chapter 12 covers such strategies.
  • Build unit tests. Build unit tests that simulate errors to see if your error-handling strategies are up to the task. Section 6.3 shows how to do this.

In the next chapter, we’ll look at a topic that can help make you a successful integration specialist, and without it, you’ll almost certainly be in trouble: testing with Camel. We’ll also look at how you can simulate errors to test whether your error handling strategies work as expected.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset