When we develop a BPMN process we have to handle correctly Error. An Error can be raised by any component during the processus instance and specially into a Work Item Handler node. This kind of node in jBPM is a Java component implementing the WorkItemHandler interface. Usually in Java program we manage error via Exception mechanism.

How to manage Exception inside jBPM WorkItemHandler custom component.

To illustrate how to use Exception/Error I propose to create a sample error process.

A quick sample

Let’s create a very common process with Work Item Handler (WIH).

This WIH has to implement org.kie.api.runtime.process.WorkItemHandler jBPM’s interface and its two methods executeWorkItem and abortWorkItem. The first method is the nominal execution. the second one is called by jBPM to reset the workitem handler action.

	@Override
	public void executeWorkItem(WorkItem workItem, WorkItemManager manager) {
		LOG.info("Nominal {}", WorkItemHandler.class);
		throw new net.a.g.jbpm.pattern.util.Exception();
	}

	@Override
	public void abortWorkItem(WorkItem workItem, WorkItemManager manager) {
		LOG.info("Abort {}", WorkItemHandler.class);

	}

As you can see this implementation throws the exception net.a.g.jbpm.pattern.util.Exception.

Java Exception to BPMN Error in jBPM engine

In BPMN, we just have to use/copy paste the full qualified exception name to be handled. jBPM engine gonna catch this exception and create an internal signal. This signal reroutes the process instance to the error branch. The signal looks like Error-${internalUniqueId}-${exceptionClassName}, and in our case Error-_72474602-751C-43FD-9D4F-2598A16468D1-net.a.g.jbpm.pattern.util.Exception (don’t care about the middle UUID it is internal and automatic).

You can map the error instance to a process variable and reuse it inside the rest next nodes as usual.

Info

In most case and in a real life, we should wrap all exceptions into normalized ones.

Unit Test to prove integration

As usual, no test no proof. Here is a common sample test. We have to register our WIH into the KieSession. We also created an listerner to illustrate all steps.

public class ExceptionToErrorTest extends JbpmJUnitBaseTestCase {
    private static final Logger LOG = LoggerFactory.getLogger(ExceptionToErrorTest.class);

    @Test
    public void test() {
        ExceptionToErrorTest.LOG.debug("jBPM unit test sample");

        final RuntimeManager runtimeManager = createRuntimeManager("net/a/g/jbpm/pattern/ExceptionToErrorProcess.bpmn");
        final RuntimeEngine runtimeEngine = getRuntimeEngine(null);
        final KieSession kieSession = runtimeEngine.getKieSession();
        kieSession.getWorkItemManager().registerWorkItemHandler("WorkItemHandler", new WorkItemHandler());

        kieSession.addEventListener(new ProcessEventListener() {

			private  Logger LOG = LoggerFactory.getLogger("net.a.g.jbpm.pattern.ProcessEventListener");

			@Override
			public void beforeProcessStarted(ProcessStartedEvent event) {
				LOG.info("Start Processus : {}", event.getProcessInstance().getProcessName());
			}

			@Override
			public void beforeNodeTriggered(ProcessNodeTriggeredEvent event) {
				LOG.info("Node Called : {}", event.getNodeInstance().getNodeName());
			}


			@Override
			public void afterProcessStarted(ProcessStartedEvent event) {
				LOG.info("End Processus : {}", event.getProcessInstance().getProcessName());

			}

		... OMIT ...
		});


        final ProcessInstance processInstance = kieSession.startProcess("ExceptionToErrorProcess");

        assertProcessInstanceNotActive(processInstance.getId(), kieSession);
        assertNodeTriggered(processInstance.getId(), "ScriptTask");
		assertNodeTriggered(processInstance.getId(), "Error End");
        
        runtimeManager.disposeRuntimeEngine(runtimeEngine);
        runtimeManager.close();
    }
}

And here is the result.

[INFO] Running net.a.g.jbpm.pattern.ExceptionToErrorTest
16:42:26.543 [main] DEBUG org.jbpm.services.task.identity.AbstractUserGroupInfo - Callback properties will be loaded from classpath:/usergroups.properties
16:42:26.550 [main] DEBUG org.jbpm.services.task.identity.JBossUserGroupCallbackImpl - Loaded properties {doctor=users,PM, Administrator=Administrators, frontDesk=users,PM, manager=managers,HR, mary=admins,managers,users,HR, admin=admins,managers,users,webdesigners,functionalanalysts, sales-rep=admin,managers,users,sales, john=admins,managers,users,PM, salaboy=admins,users, krisv=admins,managers,users}
16:42:26.551 [main] DEBUG org.jbpm.test.JbpmJUnitBaseTestCase - Configuring entire test case to have data source enabled false and session persistence enabled false with persistence unit name org.jbpm.persistence.jpa
16:42:26.560 [main] DEBUG net.a.g.jbpm.pattern.ExceptionToErrorTest - jBPM unit test sample
16:42:26.622 [main] DEBUG org.kie.api.internal.utils.ServiceDiscoveryImpl - Loading kie.conf from  jar:file:/Users/gautric/.m2/repository/org/kie/kie-internal/7.44.0.Final/kie-internal-7.44.0.Final.jar!/META-INF/kie.conf in classloader jdk.internal.loader.ClassLoaders$AppClassLoader@277050dc

... OMIT ...

16:42:28.835 [main] DEBUG org.kie.internal.runtime.manager.deploy.DeploymentDescriptorManager - No descriptor found returning default instance
16:42:28.874 [main] DEBUG org.jbpm.runtime.manager.impl.error.ExecutionErrorManagerImpl - Error handling filters [TaskExecutionErrorFilter [accepts=CannotAddTaskException, ignores=PermissionDeniedException], ProcessExecutionErrorFilter [accepts=WorkflowRuntimeException, ignores=], DBExecutionErrorFilter [accepts=SQLException,HibernateException, ignores=]]
16:42:28.874 [main] DEBUG org.jbpm.runtime.manager.impl.error.ExecutionErrorManagerImpl - Execution error storage org.jbpm.runtime.manager.impl.error.DefaultExecutionErrorStorage@2cc03cd1
16:42:28.875 [main] DEBUG org.jbpm.runtime.manager.impl.error.ExecutionErrorManagerImpl - Error event listeners java.util.ServiceLoader[org.kie.internal.runtime.error.ExecutionErrorListener]
16:42:28.876 [main] INFO org.jbpm.runtime.manager.impl.AbstractRuntimeManager - SingletonRuntimeManager is created for default-singleton
16:42:28.970 [main] INFO net.a.g.jbpm.pattern.ProcessEventListener - Start Processus : ExceptionToErrorProcess
16:42:28.980 [main] INFO net.a.g.jbpm.pattern.ProcessEventListener - Node Called : Nominal Start
16:42:28.982 [main] INFO net.a.g.jbpm.pattern.ProcessEventListener - Node Called : WorkItemHandler
16:42:28.987 [main] INFO net.a.g.jbpm.pattern.wih.WorkItemHandler - Nominal class net.a.g.jbpm.pattern.wih.WorkItemHandler
16:42:28.991 [main] DEBUG org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl - Signal Error-_72474602-751C-43FD-9D4F-2598A16468D1-net.a.g.jbpm.pattern.util.Exception received with data Exception [uuid=9aecb8c7-99af-4d41-b8fa-df97f3ae1242] in process instance 1
16:42:28.994 [main] INFO net.a.g.jbpm.pattern.wih.WorkItemHandler - Abort class net.a.g.jbpm.pattern.wih.WorkItemHandler
16:42:28.995 [main] INFO net.a.g.jbpm.pattern.ProcessEventListener - Node Called : ScriptTask
16:42:28.995 [main] ERROR net.a.g.jbpm.pattern.ScriptTask - Task Error Branch Exception [uuid=9aecb8c7-99af-4d41-b8fa-df97f3ae1242]
16:42:28.995 [main] INFO net.a.g.jbpm.pattern.ProcessEventListener - Node Called : Error End
16:42:28.996 [main] INFO net.a.g.jbpm.pattern.ProcessEventListener - End Processus : ExceptionToErrorProcess
16:42:28.997 [main] DEBUG org.drools.core.common.DefaultAgenda - State was INACTIVE is now DISPOSED
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.821 s - in net.a.g.jbpm.pattern.ExceptionToErrorTest

As you can see an internal signal is raised and received by the jBPM engine :

16:42:28.991 [main] DEBUG org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl - Signal Error-_72474602-751C-43FD-9D4F-2598A16468D1-net.a.g.jbpm.pattern.util.Exception received with data Exception [uuid=9aecb8c7-99af-4d41-b8fa-df97f3ae1242] in process instance 1

We passed also through ScriptTask Node and finish the process with the Error End meaning we finish the process with an BPMN Error Termination as expected.

It means that internally jBPMN engine handle Error as specific signal. The signal starts with Error code, followed by the UUID’s node and finishes the full qualified name of the exception.

Conclusion

Managing Java Exception with jBPMN is simple and intuitive. Of course, you can deal with the whole Java Exception API to have a very flexible, scalable mecahism. Feel free to test my sample, I let all links below, feedbacks will be also appreciate.