JBoss.orgCommunity Documentation
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.
The rest of chapter assumes that you know how to create custom <task>
nodes and how to implement and register WorkItemHandler
implementations. More
information about these topics can be found in the Domain-specific Processes chapter.
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:
Any code that is present in the process definition itself
Any code that is executed during a process and is not part of jBPM
Any code that interacts with a technical component outside of the process engine
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:
Code present in <scriptTask>
nodes or in the
jbpm-specific <onEntry>
and <onExit>
elements
Code executed in WorkItemHandlers
associated with
<task>
and task-type nodes
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.
Again, exceptions generated by a <scriptTask>
node (and other nodes
containing script code) will leave the
process unrecoverable. In fact, often, the code that starts the process
itself will end up throwing the exception generated by the business process, without returning
a reference to the process instance.
For this reason, it's important to limit the scope of the code in these nodes to operations
dealing with process variables. Using a <scriptTask>
to interact with a different technical component, such as a
database or web service has significant risks because any exceptions thrown
will corrupt or abort the process.
<task>
nodes, <serviceTask>
nodes and the rest of
the task
-type nodes are explicitly meant for interacting with other systems -- not
<scriptTask>
nodes! Use <task>
-type nodes to interact with
other technical components.
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.
Table 23.1. Exception Handling WorkItemHandler
wrapper classes
Decorator classes in the org.jbpm.bpmn2.handler package | Description |
---|---|
SignallingTaskHandlerDecorator | This class wraps an existing WorkItemHandler implementation. When the
.executeWorkItem(...) or .abortWorkItem(...) methods of the original
WorkItemHandler instance throw an exception, the
SignallingTaskHandlerDecorator will catch the exception and signal the process instance
using a configurable event type. The exception thrown will be passed as part of the event. This
functionality can be used to signal an Event SubProcess defined in the process
definition. |
LoggingTaskHandlerDecorator | This class reacts to all exceptions thrown by the .executeWorkItem(...)
or .abortWorkItem(...) WorkItemHandler methods by logging the errors. It
also saves any exceptions thrown so to an internal list so that they can be retrieved later for
inspection or further logging. Lastly, the content and format of the message logged upon an
exception are configurable. |
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:
Are you catching all possible exceptions that you want to (and no more, or less)?
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?
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?
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:
process execution will stop, and no other parts of the process will execute
the process instance will end up in an aborted state (instead of completed)
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.
The code and BPMN2 process definition shown in the next section are available in the
jbpm-examples
module. See the
org.jbpm.examples.exceptions.ExceptionHandlingErrorExample
class for the Java
code. The BPMN2 process definition is available in the
exceptions/ExceptionHandlingWithError.bpmn2
file in the
src/main/resources
directory of the jbpm-examples
module.
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.
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.
String"/> <message id="_message" itemRef="_stringItem"/>
<interface id="_serviceInterface" name="org.jbpm.examples.exceptions.service.ExceptionService"> <operation id="_serviceOperation" name="throwException"> <inMessageRef>_message</inMessageRef>
</operation> </interface> <error id="_exception" errorCode="code" structureRef="_ex
ceptionItem"/> <itemDefinition id="_exceptionItem" structureRef="org.kie
.api.runtime.process.WorkItem"/> <message id="_exceptionMessage" itemRef="_exceptionItem"/
> <interface id="_handlingServiceInterface" name="org.jbpm.examples.exceptions.service.ExceptionService"> <operation id="_handlingServiceOperation" name="handleException"> <inMessageRef>_exceptionMessage</inMessageRef>
</operation> </interface> <process id="ProcessWithExceptionHandlingError" name="Service Process" isExecutable="true" processType="Private"> <!-- properties --> <property id="serviceInputItem" itemSubjectRef="_string
Item"/> <property id="exceptionInputItem" itemSubjectRef="_exce
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>
</dataOutputAssociation> <errorEventDefinition id="_X-1_ED_1" errorRef="_exc
eption" /> </startEvent> <!-- rest of the subprocess definition... --> </subProcess> </process>
This | |
This | |
This | |
This In the process itself, a |
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
.
When signalling the jBPM process engine with an event of some sort, you should keep in mind the rules for signalling process events.
Error events can be signalled by sending an "Error-" + <the errorCode
attribute value> value to the session.
Signal events can be signalled by sending the name of the signal to the session.
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";
SignallingTaskHandlerDecorator signallingTaskWrappe
r = new SignallingTaskHandlerDecorator(ServiceTaskHandler.class, eventType); signallingTaskWrapper.setWorkItemExceptionParameter
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(); }
Here we define the name of the event that will be sent to the process instance if
the wrapped | |
Then we construct an instance of the | |
When an exception is thrown by the wrapped |
In the BPMN2 process definition above, a service interface is defined that references
the ExceptionService
class:
<interface id="_handlingServiceInterface" name="org.jbpm.examples.exceptions.service.ExceptionService">
<operation id="_handlingServiceOperation" name="handleException">
In order to fill in the blanks a little bit, the code for the ExceptionService
class has been included below. In general, you can specify any Java class with the default or an
other no-argument constructor and have it executed during a <serviceTask>
public class ExceptionService {
public static String exceptionParameterName = "my.exception.parameter.name";
public void handleException(WorkItem workItem) {
System.out.println( "Handling exception caused by work item '" + workItem.getName() + "' (id: " + workItem.getId() + ")");
Map<String, Object> params = workItem.getParameters();
Throwable throwable = (Throwable) params.get(exceptionParameterName);
throwable.printStackTrace();
}
public String throwException(String message) {
throw new RuntimeException("Service failed with input: " + message );
}
public static void setExceptionParameterName(String exceptionParam) {
exceptionParameterName = exceptionParam;
}
}
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:
It will now contain a <signalEventDefintion>
instead of a
<errorEventDefintion>
The errorRef
attribute in the <erroEventDefintion>
is
now a signalRef
attribute in the <signalEventDefintion>
.
The id
attribute in the signalRef
is of course now the id of
the <signal>
element. Before it was id of <error>
element.
Lastly, when we signal the process in the Java code, we do not signal
"Error-code
" but simply "exception-signal
", the id
of
the <signal>
element.
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.
Table 23.2. Information contained in WorkflowRuntimeException
instances.
Field name | Type | Description |
---|---|---|
processInstanceId | long | The id of the ProcessInstance instance in which the exception occurred. This
ProcessInstance may not exist anymore or be available in the database if using
persistence! |
processId | String | The id of the process definition that was used to start the process (i.e.
"ExceptionScriptTask " in
)
|
nodeId | long | The value of the (BPMN2) id attribute of the node that threw the exception. |
nodeName | String | The value of the (BPMN2) name attribute of the node that threw the exception. |
variables | Map<String, Object> | The map containing the variables in the process instance (experimental). |
message | String | The short message indicating what went wrong. |
cause | Throwable | The original exception that was thrown. |
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 23.3. BPMN2 Exception Handling Elements
BPMN2 Element types | Description |
---|---|
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.
Where are business exceptions likely to occur? There is academic research on this, but some possible examples are: