JBoss.orgCommunity Documentation

Chapter 22. Exception Management

22.1. Overview
22.2. Introduction
22.3.
22.3.1. Technical Exceptions
22.3.2. Technical Exception Examples
22.4.
22.4.1. Business Exceptions

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

The first 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.

The second section will define and explain the types of (BPMN2) exceptions that can happen or be used in a business process.

What happens to a business process when something unexpected happens during the process? Most of the time, when creating and designing a new process definition, the first step is to describe 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 unexpected 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.

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 definitions. 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 section, 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 or narrow your browser window in order to see the "callout" or note numbers on the right hand side of the code.

  <itemDefinition id="_stringItem" structureRef="java.lang.(1)String"/>
  <message id="_message" itemRef="_stringItem"/>           (2)

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

  <error id="_exception" errorCode="code" structureRef="_ex(3)ceptionItem"/>

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

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

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

    <!-- 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>
          <targetRef>exceptionInputItem</targetRef>        (4)
        </dataOutputAssociation>
        <errorEventDefinition id="_X-1_ED_1" errorRef="_exc(3)eption" />
      </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 code code 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();

        String eventType = "Error-code";                   (1)
        SignallingTaskHandlerDecorator signallingTaskWrappe(2)r 
            = new SignallingTaskHandlerDecorator(ServiceTaskHandler.class, eventType);
        signallingTaskWrapper.setWorkItemExceptionParameter(3)Name(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 a 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, the exception is then 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();
    }
 
}

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

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 22.3. BPMN2 Exception Handling Elements

BPMN2 Element typesDescription
Errors

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.

Compensation

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

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 may only be attached to activities (e.g. tasks) that could cause an exception. These Boundary events are then associated (not linked!) with a Task that will be executed if the Boundary event catches a (thrown) Compensation signal.

Start (catch) events are used when defining an Compensation Event SubProcess, which requires them in order to be able to catch a (thrown) Compensation signal.

Compensation Intermediate and End events are used in order to throw Compensation Events. These events often follow decision nodes that determine whether the workflow executed up to that point has succeeded. If not, the path including the Intermediate or End Event is chosen in order to trigger Compensatoin for the activities that did not succeed.


BPMN2 contains a number of constructs to model exceptions in business processes. There are several advantages to doing exception handling at the business process level (as opposed to handling it with code):

  • Transparency
    • Being able to quickly see what happens in exceptional situations means that the results and performance of a process is more easily monitored and measured.
    • It also increases how easily a process can be implemented as well as how maintainable a process definition is.
  • Business Logic Isolation
    • Again, the idea behind using a business process is to isolate the business logic from the technical code. This simplifies the complexity of the system and increases how quickly you can create new business processes and change existing ones.
    • Implementing exception handling at a technical level often takes more time because it's often complexer and specific to a system.