JBoss.orgCommunity Documentation

Chapter 14. Exception Management

14.1. Overview
14.2. Introduction
14.3. Business Exceptions
14.3.1. Business Exceptions elements in BPMN2
14.4. Technical Exceptions
14.4.1. Handling exceptions in WorkItemHandler instances
14.5. Technical Exception Examples
14.5.1. Example: service task handlers
14.5.2. Example: logging exceptions thrown by bad <scriptTask> nodes

This chapter will describe how to deal with unexpected behavior in your business processes using both BPMN2 and technical mechanisms.

The first section (Introduction) will define and explain the types of exceptions that can happen or be used in a business process (Business Exceptions and Technical Exceptions).

The next section will explain Technical Exceptions: we'll go through an example that uses both BPMN2 and WorkItemHandler implementations in order to isolate and handle exceptions caused by a technical component. We will also explain how to modify the example to suit other use cases.

What happens to a business process when something unexpected happens during the process? Most of the time, when you create and design a new process definition, you'll begin by describing the normative or desirable behaviour. However, a process definition that only describes all of the normal tasks and their execution order is incomplete.

The next step is to think about what might go wrong when the business process is run. What would happen if any of the human or technical actors in the process do not respond in unexpexected ways? Will any of the technical systems that the process interacts with return unexpected results -- or not return any results at all?

Deviations from the normative or "happy" flow of a business process are called exceptions. In some cases, exceptions might not be that unusual, such as trying to debit an empty bank account. However, some processes might contain many complex situations involving exceptions, all of which must be handled correctly.

Business Exceptions are exceptions that are designed and managed in the BPMN2 specification of a business process. In other words, Business Exceptions are exceptions which happen at the process or workflow level, and are not related to the technical components.

Many of the elements in BPMN2 related to Business Exceptions are related to Compensation and Business Transactions. Compensation, in particular, is complexer than many other parts of the BPMN2 specfication.

Full support for compensation and business transactions is expected with the release of jBPM 6.1 or 6.2. Once that has been implemented, this section will contain more information about using those BPMN2 features with jBPM.

The following attempts to briefly describe Compensation and Business Transaction related elements in BPMN2. For more complete information about these elements and their uses, see the BPMN2 specification, Bruce Silver's book BPMN Method and Style or any of the other available books about the use of BPMN2.

Table 14.1. BPMN2 Exception Handling Elements

BPMN2 Element typesDescription
Errors and Error Events

Error Events can be used to signal when a process has encountered an unexpected situation: signalling an error is often called throwing an error.

Boundary Error Events in a different part of the process can then be used to catch the error and initiate a sequence of activities to handle the exception.

Errors themselves can be extended with extra information that is passed from the throwing to catching event. This is done with the use of an Item Definition.

Business Transactions

A Business Transaction in BPMN2 is a subprocess which can be used with compensation. Grouping activities in a Business Transaction lets the process designer easily add exception handling to specific activities in the subprocess.

Using a Business Transaction guarantees that all activities in the transaction will have completed successfully if the Business Transaction completes successfully.

When a Business Transaction is interrupted or otherwise not completed successfully, there is a guarantee that all activities in the Business Transaction that have been initiated will be compensated if compensating activities are defined for those activities.

Compensation

Exception handling activities associated with the normal activies in a Business Transaction are triggered by Compensation Events.

Compensation Events may only be used within Business Transactions.

There are 3 types of compensation events: Intermediate (a.k.a. Boundary) (catch) events, Start (catch) events, and Intermediate or End (throw) events.

Compensation Boundary (catch) events are attached to activites (e.g. tasks) that could cause an exception. They may only be attached to activites inside a Business Transaction. If a Business Transaction fails, possibly because of the failure of one of the activities inside it, then the activities associated with Boundary (catch) events will be triggered. Only one activity or node may be associated with a Compensation Boundary Event!

Start (catch) events are used when defining an Compensation Event SubProcess inside a Business Transaction. Compensation Event SubProcesses are often used when a subprocess is needed to compensate for the Business Transaction as a whole (as opposed to defining compensating activities per node in the Business Transaction. This subprocess is triggered when a Business Transaction fails, just like activities attached to Compensation Boundary (catch) events.

Compensation Intermediate and End events are used within Business Transactions in order to throw Compensation Events. Often, logic in the Business Transaction subprocesses will determine whether or not the Business Transaction has succeeded or failed. If the subprocess has failed, then the process will proceed to an Intermediate or End Compensation Event in order to trigger compensation for the Business Transaction subprocess.

Cancel Events

Cancel Events trigger cancellation of a Business Transaction and can thus only be used with a Business Transaction.

When a Cancel Event is thrown, this indicates that the Business Transaction should be cancelled. Entities involved in the Business Transaction are then informed (via a TransactionProtocol Cancel Message) that the Business Transaction has been cancelled.

Cancellation of a Business Transaction implicitly triggers compensation of the Business Transaction.

See the sources mentioned above for the differences between Error Events (abortion of a process), Cancel Events (cancellation) and Compensate Events (compensation).


Technical exceptions happen when a technical component of a business process acts in an unexpected way. When using Java based systems, this often results in a literal Java Exception being thrown by the system.

Technical components used in a process can fail in a way that can not be described using BPMN2. In this case, it's important to handle these exceptions in expected ways.

The following types of code might throw exceptions:

However, those are somewhat abstract defintions. We can narrow down the places at which an exception might be thrown. Technical exceptions can occur at the following points:

It is much easier to ensure correct exception handling for <task> and other task-type nodes that use WorkItemHandler implementations, than for code executed directly in a <scriptTask>.

Exceptions thrown by <scriptTask> can cause the process to fail in an unrecoverable fashion. While there are certain things that you can do to contain the damage, a process that has failed in this way can not be restarted or otherwise recovered. This also applies for other nodes in a process definition that contain script code in the node definition, such as the <onEntry> and <onExit> elements.

When jBPM engine does throw an exception generated by the code in a <scriptTask> the exception thrown is a special Java exception called the WorkflowRuntimeException that contains information about the process.

WorkItemHandler classes are used when your process interacts with other technical systems. For an introduction to them and how to use them in processes, please see the Domain-specific processes chapter.

While you can build exception handling into your own WorkItemhandler implementations, there are also two “handler decorator” classes that you can use to wrap a WorkItemhandler implementation.

These two wrapper classes include logic that is executed when an exception is thrown during the execution (or abortion) of a work item.


While the two classes described above should cover most cases involving exception handling, a Java developer with some experience with jBPM should be able to create a WorkItemHandler that executes custom code upon an exception.

If you do decide to write a custom WorkItemHandler that includes exception handling logic, keep the following checklist in mind:

  1. Are you catching all possible exceptions that you want to (and no more, or less)?
  2. Are you making sure to either complete or abort the work item after an exception has been caught? If not, are there mechanisms to retry the process later? Or are incomplete process instances acceptable?
  3. What other actions should be taken when an exception is caught? Do you want to simply log the exception, or is it also important to interact with other technical systems? Do you want to trigger a (BPMN2) subprocess that will handle the exception?

Important

When you use the WorkItemManager to signal that the work item has been completed or aborted, make sure to do that after you've sent any signals to the process instance. Depending on how you've defined your process, calling WorkItemManager.completeWorkItem(...) or WorkItemManager.abortWorkItem(...) will trigger the completion of the process instance. This is because the these methods trigger the jBPM process engine to continue the process flow.

In the next section, we'll describe an example that uses the SignallingTaskHandlerDecorator to signal an event subprocess when a work item handler throws an exception.

We'll go through one example in this section, and then look quickly at how you can change it to get the behavior you want. The example involves an <error> event that's caught by an (Error) Event SubProcess.

When an Error Event is thrown, the containing process will be interrupted. This means that after the process flow attached to the error event has executed, the following will happen:

The example we'll go through contains an <error>, but at the end of the secion, we'll show how you can change the process to use a <signal> instead.

Let's look at the BPMN2 process definition first. Besides the definition of the process, the BPMN2 elements defined before the actual process definition are also important. Here's an image of the BPMN2 process that we'll be using in the example:


The BPMN2 process fragment below is part of the process shown above, and contains some notes on the different BPMN2 elements.

Note

If you're viewing this on a web browser, you may need to widen your browser window in order to see the "callout" or note numbers on the righthand side of the code.
  <itemDefinit(1)ion id="_stringItem" structureRef="java.lang.String"/>
  <message id=(2)"_message" itemRef="_stringItem"/>

  <interface id="_serviceInterface" name="org.jbpm.examples.exceptions.service.ExceptionService">
    <operation id="_serviceOperation" name="throwException">
      <inMessa(2)geRef>_message</inMessageRef>
    </operation>
  </interface>

  <error id="_(3)exception" errorCode="code" structureRef="_exceptionItem"/>

  <itemDefinit(4)ion id="_exceptionItem" structureRef="org.kie.api.runtime.process.WorkItem"/>
  <message id=(4)"_exceptionMessage" itemRef="_exceptionItem"/>

  <interface id="_handlingServiceInterface" name="org.jbpm.examples.exceptions.service.ExceptionService">
    <operation id="_handlingServiceOperation" name="handleException">
      <inMessa(4)geRef>_exceptionMessage</inMessageRef>
    </operation>
  </interface>

  <process id="ProcessWithExceptionHandlingError" name="Service Process" isExecutable="true" processType="Private">
    <!-- properties -->
    <property (1)id="serviceInputItem" itemSubjectRef="_stringItem"/>
    <property (4)id="exceptionInputItem" itemSubjectRef="_exceptionItem"/>

    <!-- main process -->
    <startEvent id="_1" name="Start" />
    <serviceTask id="_2" name="Throw Exception" implementation="Other" operationRef="_serviceOperation">

    <!-- rest of the serviceTask element and process definition... -->

    <subProcess id="_X" name="Exception Handler" triggeredByEvent="true" >
      <startEvent id="_X-1" name="subStart">
        <dataOutput id="_X-1_Output" name="event"/>
        <dataOutputAssociation>
          <sourceRef>_X-1_Output</sourceRef>
          <tar(4)getRef>exceptionInputItem</targetRef>
        </dataOutputAssociation>
        <error(3)EventDefinition id="_X-1_ED_1" errorRef="_exception" />
      </startEvent>

      <!-- rest of the subprocess definition... -->

    </subProcess>

  </process>
  

1

This <itemDefinition> element defines a data structure that we then use in the serviceInputItem property in the process.

2

This <message> element (1rst reference) defines a message that has a String as its content (as defined by the <itemDefintion> element on line above). The <interface> element below it refers to it (2nd reference) in order to define what type of content the service (defined by the <interface>) expects.

3

This <error> element (1rst reference) defines an error for use later in the process: an Event SubProcess is defined that is triggered by this error (2nd reference). The content of the error is defined by the <itemDefintion> element defined below the <error> element.

4

This <itemDefintion> element (1rst reference) defines an item that contains a WorkItem instance. The <message> element (2nd reference) then defines a message that uses this item definition to define its content. The <interface> element below that refers to the <message> definition (3rd reference) in order to define the type of content that the service expects.

In the process itself, a <property> element (4th reference) is defined as having the content defined by the initial <itemDefintion>. This is helpful because it means that the Event SubProcess can then store the error it receives in that property (5th reference).

Caution

When you're using a <serviceTask> to call a Java class, make sure to double check the class name in your BPMN2 definition! A small typo there can cost you time later when you're trying to figure out what went wrong.

Now that BPMN2 process definition is (hopefully) a little clearer, we can look at how to set up jBPM to take advantage of the above BPMN2.

In the (BPMN2) process definition above, we define two different <serviceTask> activities. The org.jbpm.bpmn2.handler.ServiceTaskHandler class is the default task handler class used for <serviceTask> tasks. If you don't specify a WorkItemHandler implementation for a <serviceTask>, the ServiceTaskHandler class will be used.

In the code below, you'll see that we actually wrap or decorate the ServiceTaskHandler class with a SignallingTaskHandlerDecorator instance. We do this in order to define the what happens when the ServiceTaskHandler throws an exception.

In this case, the ServiceTaskHandler will throw an exception because it's configured to call the ExceptionService.throwException method, which throws an exception. (See the _handlingServiceInterface <interface> element in the BPMN2.)

In the code below, we also configure which (error) event is sent to the process instance by the SignallingTaskHandlerDecorator instance. The SignallingTaskHandlerDecorator does this when an exception is thrown in a task. In this case, since we've defined an <error> with the error codecode” in the BPMN2, we set the signal to Error-code.



import java.util.HashMap;
import java.util.Map;
import org.jbpm.bpmn2.handler.ServiceTaskHandler;
import org.jbpm.bpmn2.handler.SignallingTaskHandlerDecorator;
import org.jbpm.examples.exceptions.service.ExceptionService;
import org.kie.api.KieBase;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.process.ProcessInstance;
import org.kie.internal.builder.KnowledgeBuilder;
import org.kie.internal.builder.KnowledgeBuilderFactory;
import org.kie.internal.io.ResourceFactory;
public class ExceptionHandlingErrorExample {
    public static final void main(String[] args) {
        runExample();
    }
    public static ProcessInstance runExample() {
        KieSession ksession = createKieSession();
(1)        String eventType = "Error-code";
(2)        SignallingTaskHandlerDecorator signallingTaskWrapper 
            = new SignallingTaskHandlerDecorator(ServiceTaskHandler.class, eventType);
(3)        signallingTaskWrapper.setWorkItemExceptionParameterName(ExceptionService.exceptionParameterName);
        ksession.getWorkItemManager().registerWorkItemHandler("Service Task", signallingTaskWrapper);
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("serviceInputItem", "Input to Original Service");
        ProcessInstance processInstance = ksession.startProcess("ProcessWithExceptionHandlingError", params);
        
        return processInstance;
    }
    private static KieSession createKieSession() {
        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
        kbuilder.add(ResourceFactory.newClassPathResource("exceptions/ExceptionHandlingWithError.bpmn2"), ResourceType.BPMN2);
        KieBase kbase = kbuilder.newKnowledgeBase();
        return kbase.newKieSession();
    }
  

1

Here we define the name of the event that will be sent to the process instance if the wrapped WorkItemHandler implementation throws an exception. The eventType string is used when instantiating the SignallingTaskHandlerDecorator class.

2

Then we construct an instance of the SignallingTaskHandlerDecorator class. In this case, we simply give it the class name of the WorkItemHandler implementation class to instantiate, but another constructor is available that we can pass an instance of a WorkItemHandler implementation to (necessary if the WorkItemHandler implementation does not have a no-argument constructor).

3

When an exception is thrown by the wrapped WorkItemHandler, the SignallingTaskHandlerDecorator saves it as a parameter in the WorkItem instance with a parameter name that we configure the SignallingTaskHandlerDecorator to give it (see the code below for the ExceptionService).

In the example above, the thrown Error Event interrupts the process: no other flows or activities are executed once the Error Event has been thrown.

However, when a Signal Event is processed, the process will continue after the Signal Event SubProcess (or whatever other activities that the Signal Event triggers) has been executed. Furthermore, this implies that the the process will not end up in an aborted state, unlike a process that throws an Error Event.

In the process above, we use the <error> element in order to be able to use an Error Event:


  <error id="_exception" errorCode="code" structureRef="_exceptionItem"/>

When we want to use a Signal Event instead, we remove that line and use a <signal> element:


   <signal id="exception-signal" structureRef="_exceptionItem"/> 

However, we must also change all references to the "_exception" <error> so that they now refer to the "exception-signal" <signal>.

That means that the <errorEventDefintion> element in the <startEvent>,


   <errorEventDefinition id="_X-1_ED_1" errorRef="_exception" /> 

must be changed to a <signalEventDefintion> which would like like this:


   <signalEventDefinition id="_X-1_ED_1" signalRef="exception-signal"/> 

In short, we have to make the following changes to the <startEvent> in the Event SubProcess:

In this section, we'll briefly describe what's possible when dealing with <scriptTask> nodes that throw exceptions, and then quickly go through an example (also available in the jbpm-examples module) that illustrates this.

If you're reading this, then you probably already have problem: you're either expecting to run into this problem because there are scripts in your process definition that might throw an exception, or you're already running a process instance with scripts that are causing a problem.

Unfortunately, if you're running into this problem, then there is not much you can do. The only thing that you can do is retrieve more information about exactly what's causing the problem. Luckily, when a <scriptTask> node causes an exception, it's wrapped in a WorkflowRuntimeException.

What type of information is available? The WorkflowRuntimeException instance will contain the information outlined in the following table. All of the fields listed are available via the normal get* methods.


The following code illustrates how to extract extra information from a process instance that throws a WorkflowRuntimeException exception instance.



import org.jbpm.workflow.instance.WorkflowRuntimeException;
import org.kie.api.KieBase;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.process.ProcessInstance;
import org.kie.internal.builder.KnowledgeBuilder;
import org.kie.internal.builder.KnowledgeBuilderFactory;
import org.kie.internal.io.ResourceFactory;
public class ScriptTaskExceptionExample {
    public static final void main(String[] args) {
        runExample();
    }
    public static void runExample() {
        KieSession ksession = createKieSession();
        Map<String, Object> params = new HashMap<String, Object>();
        String varName = "var1";
        params.put( varName , "valueOne" );
        try { 
            ProcessInstance processInstance = ksession.startProcess("ExceptionScriptTask", params);
        } catch( WorkflowRuntimeException wfre ) { 
            String msg = "An exception happened in "
                    + "process instance [" + wfre.getProcessInstanceId()
                    + "] of process [" + wfre.getProcessId()
                    + "] in node [id: " + wfre.getNodeId() 
                    + ", name: " + wfre.getNodeName()
                    + "] and variable " + varName + " had the value [" + wfre.getVariables().get(varName)
                    + "]";
            System.out.println(msg);
        }
    }
    
    private static KieSession createKieSession() {
        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
        kbuilder.add(ResourceFactory.newClassPathResource("exceptions/ScriptTaskException.bpmn2"), ResourceType.BPMN2);
        KieBase kbase = kbuilder.newKnowledgeBase();
        return kbase.newKieSession();
    }
 
}