Nav
You are viewing an older version of this section. Click here to navigate to the latest version.

Exception Strategy Most Common Use Cases

This page illustrates six ways to approach error handling within Mule ESB applications. We cover the following examples:

All the examples presented here demonstrate error handling techniques within the context of Mule Flows. Although certain terms and other details differ for Mule Service Models — which were super-ceded by Mule Flows as of version 3 of Mule ESB — the same error handling principles apply to both architectures.

The first two examples implement a transactional JMS queue and include a message transformer that appends a string to the original message. When an error occurs, the message, in one form or another, is routed through the flow’s exception strategy and into the JMS dead letter queue, where it can be recovered and examined to determine why the error occurred.

These two examples differ principally in the “message state” they send to the dead letter queue.

Route the message (as it existed just before the error occurred) to the dead letter queue

In this first case, we recover for examination the message in its final state before the error occurred. In other words, the message has already been transformed (i.e., a string has been appended to the payload), but the exception has not yet been thrown.

The in-line numbers (i.e., <!-- [1] -→ for XML and //1 for Java) that conclude certain lines of code correspond to the numbered notes that appear immediately beneath those code listings.

         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<flow name="LastMessageStateRouting">
    <jms:inbound-endpoint queue="in">
        <jms:transaction action="ALWAYS_BEGIN" />
    </jms:inbound-endpoint>
    <append-string-transformer message=" with some text added"/>
    <test:component throwException="true"/>
    <jms:outbound-endpoint queue="out">
        <jms:transaction action="ALWAYS_JOIN" />
    </jms:outbound-endpoint>
    <default-exception-strategy>
        <commit-transaction exception-pattern="*"/> <!-- [1] -->
        <processor-chain>
            <expression-transformer evaluator="groovy" expression="payload.getPayload()"/> <!-- [2] -->
            <jms:outbound-endpoint queue="dead.letter"> <!-- [3] -->
                <jms:transaction action="ALWAYS_JOIN" />
            </jms:outbound-endpoint>
        </processor-chain>
    </default-exception-strategy>
</flow>
  1. To avoid the possibility of inadvertently processing the same message more than once, we configure the exception strategy to commit the transaction, no matter what type of exception is thrown.

  2. We replace the current payload with the version of the payload (i.e., the transformed payload with the specified string appended) that emerged from the Append String transformer just before the exception was thrown.

  3. We send that transformed version of the message to the JMS dead letter queue. To accomplish this, we "join" the transaction (i.e., resume the transaction with the version of the message that existed just before the exception was thrown) at the outbound endpoint embedded in the default exception strategy. The commit is performed after we route the message through all the exception strategy message processors.

Route the message (in its initial state, when it triggered the flow) to the dead letter queue

In this second case, we don’t want to examine the message in its transformed state, but rather, in its initial, untransformed state when it emerged from the flow’s message source, thus triggering the message processing flow. To ensure that we can send this original form of the message to the dead letter queue, we need to save it temporarily in the session attribute, where it will be available should an exception occur.


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<flow name="OriginalMessageRouting">
    <jms:inbound-endpoint queue="in">
        <jms:transaction action="ALWAYS_BEGIN" />
    </jms:inbound-endpoint>
    <message-properties-transformer scope="session"> <!-- [1] -->
        <add-message-property key="originalMessage" value="#[message:payload]"/>
    </message-properties-transformer>
    <append-string-transformer message=" with some text added"/>
    <test:component/>
    <jms:outbound-endpoint queue="out">
        <jms:transaction action="ALWAYS_JOIN" />
    </jms:outbound-endpoint>
    <default-exception-strategy>
        <commit-transaction exception-pattern="*"/> <!-- [2] -->
        <processor-chain>
            <expression-transformer evaluator="header"  expression="SESSION:originalMessage"/>  <!-- [3] -->
            <message-properties-transformer scope="session">  <!-- [4] -->
                <delete-message-property key="originalMessage"/>
            </message-properties-transformer>
            <jms:outbound-endpoint queue="dead.letter">  <!-- [5] -->
                <jms:transaction action="ALWAYS_JOIN" />
            </jms:outbound-endpoint>
        </processor-chain>
    </default-exception-strategy>
</flow>
  1. To preserve the original message, we must store it in a session property; thus, we won’t lose it no matter what happens during the rest of the flow.

  2. Regardless of the exception type, we want to commit the transaction so we don’t process this message again.

  3. At this point, we replace the current payload with the one saved in the session property.

  4. It’s important to remove the session property for originalMessage to avoid propagating it to other flows, along with the attendant performance hit.

  5. Send the message (in its original state) tot he dead letter queue, then commit the transaction so this message won’t get processed twice.

Route the message based on exception type

Sometimes, we execute different actions depending on the type of exception that has been thrown. This is analogous to content-based routing, but in this particular case, routing is determined by the type of exception thrown, rather than the type of payload.


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<jms:activemq-connector name="jmsConnector" maxRedelivery="3" /> <!-- [1] -->

<flow name="RouteByExceptionType">
    <jms:inbound-endpoint queue="in">
        <jms:transaction action="ALWAYS_BEGIN" />
    </jms:inbound-endpoint>
    <test:component/>
    <jms:outbound-endpoint queue="out">
        <jms:transaction action="ALWAYS_JOIN" />
    </jms:outbound-endpoint>
    <default-exception-strategy>
        <commit-transaction exception-pattern="org.mule.transport.jms.redelivery.MessageRedeliveredException"/> <!-- [2] -->
        <choice>
            <when evaluator="groovy" expression='payload.getException() instanceof org.mule.transport.jms.redelivery.MessageRedeliveredException'> <!-- [3] -->
                <expression-transformer evaluator="groovy" expression="payload.getPayload()"/>
                <jms:outbound-endpoint queue="dead.letter">
                    <jms:transaction action="ALWAYS_JOIN" />
                </jms:outbound-endpoint>
            </when>
            <when evaluator="groovy" expression="payload.getException() instanceof org.mule.component.ComponentException"> <!-- [4] -->
                <jms:outbound-endpoint queue="exceptions">
                    <jms:transaction action="NONE"/>
                </jms:outbound-endpoint>
            </when>
            <otherwise> <!-- [5] -->
                <logger/>
            </otherwise>
        </choice>
    </default-exception-strategy>
</flow>
  1. We start by specifying maxRedelivery="3" for the inbound endpoint connector so that Mule attempts to send the message through the flow no more than 3 times. After the third failure, Mule throws MessageRedeliveredException.

  2. The transaction is committed if and only if the thrown exception is org.mule.transport.jms.redelivery.MessageRedeliveredException.

  3. If the exception turns out to be MessageRedeliveredException, we don’t want to reprocess the message we haven’t been able to deliver, so we send the processed message to the JMS dead letter queue, then commit the transaction.

  4. If the exception is thrown by the Component (typically, the custom-coded business logic at the heart of the Mule Flow), we send the ExceptionMessage to the JMS exceptions queue.

  5. For all other types of exceptions, we simply log the exception.

Roll back the transaction and send a notification

When an exception is thrown for some flows, we want to rollback the transaction and send out a notification containing information about the failure. This example uses an SMTP endpoint so that notification can take place by email.


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
<flow name="RollbackTransactionAndSendEmail">
    <jms:inbound-endpoint queue="in">
        <jms:transaction action="ALWAYS_BEGIN"/>
    </jms:inbound-endpoint>
    <test:component throwException="true"/>
    <default-exception-strategy>
        <rollback-transaction exception-pattern="*"/> <!-- [1] -->
        <processor-chain>
            <expression-transformer evaluator="groovy" expression='"Failed to process message: " + payload.getPayload()'/> <!-- [2] -->
            <smtp:outbound-endpoint user="pablolagreca" password="mypassword" host="smtp.gmail.com" from="failures-app@mycompany.com" to="technical-operations@mycompany.com" subject="Message Failure"/>  <!-- [3] -->
        </processor-chain>
    </default-exception-strategy>
</flow>
  1. Whenever an exception gets thrown, regardless of type, roll back the transaction.

  2. Aggregate the payload of the message we will send as the email notification. Typically, we insert the following two parts into the message:

    • the message being processed

    • the exception message

  3. This is how we configure the outbound endpoint that sends the notification email.

Prevent the flow from receiving any more messages after certain types of exceptions occur

Sometimes when an external service becomes unavailable, and we know that every message processing attempt will fail until the unavailable resource is restored, we want to shut down the flow to prevent it from consuming any more messages.


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<flow name="StopFlowBasedOnExceptionType">
    <vm:inbound-endpoint path="in" exchange-pattern="request-response"/>
    <http:outbound-endpoint host="localhost" port="808" responseTimeout="5"/>
    <default-exception-strategy>
        <choice>
            <when evaluator="groovy" expression="payload.getException().getCause() instanceof java.net.ConnectException"> <!-- [1] -->
                <script:component>
                    <script:script engine="groovy">
                        flowConstruct.stop();
                    </script:script> </script:component>
            </when> <otherwise> <!-- [2] --> <logger/>
            </otherwise> </choice>
    </default-exception-strategy> </flow>
  1. If the exception type is ConnectionException, we want to stop the flow.

  2. For all other exception types, we just log the exception.

Invoke a custom exception strategy implemented through a Java class

By default, when an exception is thrown for a flow (or service) based on a request-response exchange pattern, the caller receives NullPayload as the message payload and an ExceptionPayload (i.e., the payload as it existed immediately after the exception was thrown) as the exceptionPayload. The only way to change this behavior is by creating a Java class to implement a custom exception strategy.


         
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class PreservePayloadExceptionStrategy extends AbstractMessagingExceptionStrategy
{
    public PreservePayloadExceptionStrategy(MuleContext muleContext)
    {
        super(muleContext);
    }

    private MuleEvent processException(Exception e, MuleEvent event, RollbackSourceCallback rollbackCallback)
    {
        Object payloadBeforeException = event.getMessage().getPayload(); //2
        MuleEvent resultEvent = super.handleException(e, event, rollbackCallback); //3
        resultEvent.getMessage().setPayload(payloadBeforeException); //4
        return resultEvent; //5
    }

    @Override
    public MuleEvent handleException(Exception e, MuleEvent event) //1
    {
        return processException(e, event, null);
    }

    @Override
    public MuleEvent handleException(Exception e, MuleEvent event, RollbackSourceCallback rollbackCallback) //1
    {
        return processException(e, event, rollbackCallback);
    }
}
  1. We must override handleException(Exception e, MuleEvent event) and handleException(Exception ex, MuleEvent event, RollbackSourceCallback rollbackCallback), which have been inherited from AbstractMessagingExceptionStrategy, since those are the methods Mule calls to handle every exception thrown.

  2. We use a local variable to store a copy of the payload as it existed just before the exception occurred.

  3. We call super.handleException(e, event, rollbackCallback) to invoke the default exception strategy.

  4. We replace payload that existed after the exception was thrown with a copy of the payload as it existed just before the exception occurred.

  5. We return the event with the updated payload.

After creating our custom exception strategy, we can use it in a flow (or service model), as illustrated below:


         
      
1
2
3
4
5
6
7
8
<flow name="PreservePayloadExceptionStrategy">
    <vm:inbound-endpoint path="in" exchange-pattern="request-response"/>
    <append-string-transformer message=" with some text added"/>
    <test:component throwException="true"/>
    <custom-exception-strategy class="org.mule.examples.PreservePayloadExceptionStrategy"> <!-- [1] -->
        <logger/>
    </custom-exception-strategy>
</flow>
  1. Define a custom exception strategy implemented by the class PreservePayloadExceptionStrategy. This allows us to send the caller a copy of the message as it existed when the exception was thrown instead of sending an ExceptionPayload.