BPMN 2.0 Implementation Reference

BPMN 2.0 Overview and Coverage

This page gives you an overview of BPMN 2.0 elements and the current coverage of the process engine.

Heads up! If you are unfamilliar with BPMN 2.0 you might want to check out our BPMN Tutorial first.

The elements marked in orange are supported. Hover over the element to see from which version on they are supported.

Participants

Subprocesses

Gateways

Data

Artifacts

Events

In BPMN there are Start events, intermediate events, and end events. Those three event types can be catching and/or throwing. Intermediate events might be used as boundary events on tasks, then they can be interrupting or non-interrupting. This gives you a lot of flexibility to use events in your processes.

Heads up! For understanding the principle behavior of events in BPMN we recommend to check out Events: Basic Concepts.
Type Start Intermediate End
Normal Event Subprocess Event Subprocess
non-interrupt
catch boundary boundary
non-interrupt
throw
None
Message
Timer
Conditional
Link
Signal
Error
Escalation
Termination
Compensation
Cancel
Multiple
Multiple Parallel

BPMN 2.0 Custom Extensions

The BPMN 2.0 standard is a good thing for all parties involved. End-users don't suffer from a vendor lock-in that comes by depending on a proprietary solution. Frameworks, and particularly open-source frameworks such as camunda BPM, can implement a solution that has the same (and often better implemented ;-)) features as those of a big vendor. Due to the BPMN 2.0 standard, the transition from one tool to the other works.

The downside of a standard however, is the fact that it is always the result of many discussions and compromises between different companies (and often visions). As a developer reading the BPMN 2.0 XML of a process definition, sometimes it feels like certain constructs or way to do things are too cumbersome. Since camunda BPM puts ease of development as a top-priority, we introduced own BPMN extensions. These extensions are new constructs or ways to simplify certain constructs, that are not in the BPMN 2.0 specification.

Although the BPMN 2.0 specification clearly states that it was made for custom extension, we make sure that:

  • The prerequisite of such a custom extension is that there always must be a simple transformation to the standard way of doing things. So when you decide to use a custom extension, you don't have to be afraid that there is no way back.
  • When using a custom extension, this is always clearly indicated by giving the new XML element, attribute, etc. the camunda: namespace prefix.
  • The goal of these extensions is to eventually push them back into a next version of the BPMN specification, or at least trigger a discussion that can lead to a revision of that specific BPMN construct.

So whether you want to use a custom extension or not, is completely up to you. Several factors will influence this decision (graphical editor usage, company policy, etc.). We only provide them since we believe that some points in the standard can be done simpler or more efficient. Feel free to give us (positive and/or negative) feedback on our extensions, or to post new ideas for custom extensions. Who knows, some day your idea might pop up in the specification.

Service Task

A service task is used to invoke services. In camunda this is done by calling Java code.

There are 4 ways of declaring how to invoke Java logic:

  • Specifying a class that implements JavaDelegate or ActivityBehavior
  • Evaluating an expression that resolves to a delegation object
  • Invoking a method expression
  • Evaluating a value expression

To specify a class that is called during process execution, the fully qualified classname needs to be provided by the camunda:class attribute.

<serviceTask id="javaService" 
             name="My Java Service Task" 
             camunda:class="org.camunda.bpm.MyJavaDelegate" />

Please refer to Java Delegate for details on how to implement a Java Delegate.

It is also possible to use an expression that resolves to an object. This object must follow the same rules as objects that are created when the camunda:class attribute is used.

<serviceTask id="serviceTask" camunda:delegateExpression="${delegateExpressionBean}" />

Here, the delegateExpression is an expression that resolves to a bean implementing the JavaDelegate interface. Beans can be resolved for example via CDI or Spring (see below).

To specify an expression that should be evaluated, use attribute camunda:expression.

<serviceTask id="javaService" 
             name="My Java Service Task" 
             camunda:expression="#{printer.printMessage()}" />

Method printMessage (without parameters) will be called on the named bean called printer.

It's also possible to pass parameters with an method used in the expression.

<serviceTask id="javaService" 
             name="My Java Service Task" 
             camunda:expression="#{printer.printMessage(execution, myVar)}" />

Method printMessage will be called on the object named printer. The first parameter passed is the DelegateExecution, which is available in the expression context by default available as execution. The second parameter passed, is the value of the variable with name myVar in the current execution.

To specify a UEL value expression that should be evaluated, use attribute camunda:expression.

<serviceTask id="javaService" 
             name="My Java Service Task" 
             camunda:expression="#{split.ready}"
             camunda:resultVariable="myVar" />

The getter method of property ready, getReady() (without parameters), will be called on the named bean called split. The named objects are resolved in the execution's process variables and (if applicable) in the CDI or Spring context.

Note that you can store the return value of the method in a process variable by specifying the camunda:resultVariable.

Using Java Delegate Class

Please refer to Java Delegate for details.

Generic Java Delegates & Field Injection

You can easily write generic Java Delegate classes with can later on be configured via the BPMN 2.0 XML in the Service Task. Please refer to Field Injection for details.

Service task results

The return value of a service execution (for service task using expression only) can be assigned to an already existing or to a new process variable by specifying the process variable name as a literal value for the camunda:resultVariable attribute of a service task definition. Any existing value for a specific process variable will be overwritten by the result value of the service execution. When not specifying a result variable name, the service execution result value gets ignored.

<serviceTask id="aMethodExpressionServiceTask"
           camunda:expression="#{myService.doSomething()}"
           camunda:resultVariable="myVar" />

In the example above, the result of the service execution (the return value of the doSomething() method invocation on object myService) is set to the process variable named myVar after the service execution completes.

Result variables and multi-instance

Note that when you use camunda:resultVariable in a multi-instance construct, for example in a multi-instance subprocess, the result variable is overwritten every time the task completes, which may appear as random behavior.

This is a known issue. As a workaround, a local variable can be declared by adding an execution listener to the subprocess' start event that initializes the variable as null.

Additional Resources

Send Task

A send task is used to send a message. In camunda this is done by calling Java code.

The send task has the same behaviour as a service task.

<sendTask id="sendTask" camunda:class="org.camunda.bpm.MySendTaskDelegate" />

User Task

A user task is used to model work that needs to be done by a human actor. When the process execution arrives at such a user task, a new task is created in the task list of the user(s) or group(s) assigned to that task.

A user task is defined in XML as follows. The id attribute is required, the name attribute is optional.

<userTask id="theTask" name="Important task" />

Description

A user task can have also a description. In fact any BPMN 2.0 element can have a description. A description is defined by adding the documentation element.

<userTask id="theTask" name="Schedule meeting" >
  <documentation>
      Schedule an engineering meeting for next week with the new hire.
  </documentation>

The description text can be retrieved from the task in the standard Java way:

task.getDescription();

Due Date

Each task has a field, indicating the due date of that task. The Query API can be used to query for tasks that are due on, before or after a certain date.

There is an activity extension which allows you to specify an expression in your task-definition to set the initial due date of a task when it is created. The expression should always resolve to a java.util.Date, java.util.String (ISO8601 formatted) or null. When using ISO8601 formatted Strings, you may either specify an exact point in time or a time period relative to the time the task is created. For example, you could use a date that was entered in a previous form in the process or calculated in a previous Service Task.

<userTask id="theTask" name="Important task" camunda:dueDate="${dateVariable}"/>

The due date of a task can also be altered using the TaskService or in TaskListeners using the passed DelegateTask.

User Assignment

A user task can be directly assigned to a user. This is done by defining a humanPerformer sub element. Such a humanPerformer definition needs a resourceAssignmentExpression that actually defines the user. Currently, only formalExpressions are supported.

<process ... >
  ...  
  <userTask id='theTask' name='important task' >
    <humanPerformer>
      <resourceAssignmentExpression>
        <formalExpression>kermit</formalExpression>
      </resourceAssignmentExpression>
    </humanPerformer>
  </userTask>

Only one user can be assigned as human performer to the task. In the engine terminology, this user is called the assignee. Tasks that have an assignee are not visible in the task lists of other people and can be found in the so-called personal task list of the assignee instead.

Tasks directly assigned to users can be retrieved through the TaskService as follows:

List<Task> tasks = taskService.createTaskQuery().taskAssignee("kermit").list();

Tasks can also be put in the so-called candidate task list of people. In that case, the potentialOwner construct must be used. The usage is similar to the humanPerformer construct. Do note that it is required to define for each element in the formal expression to specify if it is a user or a group (the engine cannot guess this).

<process ... >
  ...
  <userTask id='theTask' name='important task' >
    <potentialOwner>
      <resourceAssignmentExpression>
        <formalExpression>user(kermit), group(management)</formalExpression>
      </resourceAssignmentExpression>
    </potentialOwner>
  </userTask>

Tasks defines with the potential owner construct, can be retrieved as follows (or a similar TaskQuery usage as for the tasks with an assignee):

List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit");

This will retrieve all tasks where kermit is a candidate user, i.e. the formal expression contains user(kermit). This will also retrieve all tasks that are assigned to a group where kermit is a member of (e.g. group(management), if kermit is a member of that group and the identity component is used). The groups of a user are resolved at runtime and these can be managed through the IdentityService.

If no specifics are given whether the given text string is a user or group, the engine defaults to group. So the following two alternatives are the same:

<formalExpression>accountancy</formalExpression>
<formalExpression>group(accountancy)</formalExpression>

User Assignment using custom extensions

It is clear that user and group assignments are quite cumbersome for use cases where the assignment is not complex. To avoid these complexities, custom extensions on the user task are possible.

  • assignee attribute: this custom extension allows to directly assign a user task to a given user.

    <userTask id="theTask" name="my task" camunda:assignee="kermit" />

    This is exactly the same as using a humanPerformer construct as defined above.

  • candidateUsers attribute: this custom extension allows to make a user a candidate for a task.

    <userTask id="theTask" name="my task" camunda:candidateUsers="kermit, gonzo" />

    This is exactly the same as using a potentialOwner construct as defined above. Note that it is not required to use the user(kermit) declaration as is the case with the potential owner construct, since the attribute can only be used for users.

  • candidateGroups attribute: this custom extension allows to make a group a candidate for a task.

    <userTask id="theTask" name="my task" camunda:candidateGroups="management, accountancy" />

    This is exactly the same as using a potentialOwner construct as defined above. Note that it is not required to use the group(management) declaration as is the case with the potential owner construct, since the attribute can only be used for groups.

  • candidateUsers and candidateGroups can both be defined on the same user task.

Note: Although the camunda engine provides an identity management component, which is exposed through the IdentityService, no check is done whether a provided user is known by the identity component. This allows to integrate the engine with existing identity management solutions when it is embedded into an application.

In case the previous approaches are not sufficient, it is possible to delegate to custom assignment logic using a task listener on the create event:

<userTask id="task1" name="My task" >
  <extensionElements>
    <camunda:taskListener event="create" class="org.camunda.bpm.MyAssignmentHandler" />
  </extensionElements>
</userTask>

The DelegateTask that is passed to the TaskListener implementation, allows to set the assignee and candidate-users/groups:

public class MyAssignmentHandler implements TaskListener {
  public void notify(DelegateTask delegateTask) {
    // Execute custom identity lookups here    
    // and then for example call following methods:
    delegateTask.setAssignee("kermit");
    delegateTask.addCandidateUser("fozzie");
    delegateTask.addCandidateGroup("management");
    ...
  } 
}

When using Spring or CDI it is possible to use the custom assignment attributes as described in the section above, and delegate to a bean using a task listener with an expression that listens to task create events. In the following example, the assignee will be set by calling the findManagerOfEmployee on the ldapService Spring/CDI bean. The emp parameter that is passed, is a process variable>.

<userTask id="task" name="My Task" camunda:assignee="${ldapService.findManagerForEmployee(emp)}"/>

This also works similar for candidate users and groups:

<userTask id="task" name="My Task" camunda:candidateUsers="${ldapService.findAllSales()}"/>

Note that this will only work if the return type of the invoked methods is String or Collection (for candidate users and groups):

public class FakeLdapService {

  public String findManagerForEmployee(String employee) {
    return "Kermit The Frog";
  }

  public List<String> findAllSales() {
    return Arrays.asList("kermit", "gonzo", "fozzie");
  }
}

Forms

It is possible to provide information to render a user task form by using the camunda:formKey attribute:

<userTask id="someTask" camunda:formKey="someForm.html">
  ...
</userTask>

The form key is a symbolic value which can be set in the BPMN XML file by using the extension attribute formKey and retrieved at runtime using the process engine API.

If the user task form is displayed inside the Camunda Tasklist, the format of the formKey must follow special rules. See the corresponding section in the user guide for details.

In custom applications, the value of the form key can be chosen freely. In a custom application the value of the form key attribute can be interpreted freely. Based on the specific UI technology used, it can reference the name of an HTML file, a JSF / Facelets template, a Vaadin / GWT view, ...

Retrieving the form key using the form service.

String formKey = formService.getTaskFormData(someTaskId).getFormKey();

Business Rule Task

A Business Rule task is used to synchronously execute one or more rules.

You might use the rule engine of your choice, on the open source side we made good experiences with JBoss Drools. Therefor You have to plug in your implementation of the rule task exactly like in a Service Task. So the difference is only that it has a different icon in the BPMN process model - but this can make already a huge difference for acceptance of the process model.

<businessRuleTask id="businessRuleTask" camunda:class="${MyRuleServiceDelegate}" />

Additional Resources

Script Task

A script task is an automatic activity. When a process execution arrives at the script task, the corresponding script is executed.

A script task is defined by specifying the script and the scriptFormat.

<scriptTask id="theScriptTask" name="Execute script" scriptFormat="groovy">
  <script>
    sum = 0
    for ( i in inputArray ) {
      sum += i
    }
  </script>
</scriptTask>

The value of the scriptFormat attribute must be a name that is compatible with the JSR-223 (scripting for the Java platform). If you want to use a (JSR-223 compatible) scripting engine, it is necessary to add the corresponding jar to the classpath and use the appropriate name.

Supported Script Languages:

Currently the camunda BPM platform supports Groovy scripts only. We are working on making it compatible with JSR-223!

Variables in scripts

All process variables that are accessible through the execution that arrives in the script task, can be used within the script. In the example, the script variable inputArray is in fact a process variable (an array of integers).

<script>
    sum = 0
    for ( i in inputArray ) {
      sum += i
    }
</script>

It's also possible to set process variables in a script, simply by using an assignment statement. In the example above, the sum variable will be stored as a process variable after the script task has been executed. To avoid this behavior, script-local variables can be used. In Groovy, the keyword def must then be used: def sum = 0. In that case, no process variable will be stored.

An alternative is to set variables through the current execution, which is available as a reserved variable called execution.

<script>
    def scriptVar = "test123"
    execution.setVariable("myVar", scriptVar)
</script>

Note: the following names are reserved and cannot be used as variable names: out, out:print, lang:import, context, elcontext.

Script results

The return value of a script task can be assigned to an already existing or to a new process variable by specifying the process variable name as a literal value for the camunda:resultVariable attribute of a script task definition. Any existing value for a specific process variable will be overwritten by the result value of the script execution. When not specifying a result variable name, the script result value gets ignored.

<scriptTask id="theScriptTask" name="Execute script" scriptFormat="juel" camunda:resultVariable="myVar">
  <script>#{echo}</script>
</scriptTask>

In the above example, the result of the script execution (the value of the resolved expression #{echo}) is set to the process variable named myVar after the script completes.

Result variables and multi-instance

Note that when you use camunda:resultVariable in a multi-instance construct, for example in a multi-instance subprocess, the result variable is overwritten every time the task completes, which may appear as random behavior.

This is a known issue. As a workaround, a local variable can be declared by adding an execution listener to the subprocess' start event that initializes the variable as null.

Receive Task

A Receive Task is a simple task that waits for the arrival of a certain message. Currently, we have only implemented Java semantics for this task. When process execution arrives at a Receive Task, the process state is committed to the persistence store. This means that the process will stay in this wait state, until a specific message is received by the engine, which triggers the continuation of the process past the Receive Task.

<receiveTask id="waitState" name="wait" />

To continue a process instance that is currently waiting at such a Receive Task, the runtimeService.signal(executionId) must be called using the id of the execution that arrived in the Receive Task. The following code snippet shows how this works in practice:

ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");
Execution execution = runtimeService.createExecutionQuery()
  .processInstanceId(pi.getId())
  .activityId("waitState")
  .singleResult();
assertNotNull(execution);

runtimeService.signal(execution.getId());

Additional Resources

Manual Task

A Manual Task defines a task that is external to the BPM engine. It is used to model work that is done by somebody, which the engine does not need to know of, nor is there a system or UI interface. For the engine, a manual task is handled as a pass-through activity, automatically continuing the process from the moment process execution arrives into it.

<manualTask id="myManualTask" name="Manual Task" />

Task Markers

In addition to those various types of tasks, we can mark tasks as loops, multiple instances, or compensations. Markers can be combined with task types.

Multiple Instance

A multi-instance activity is a way of defining repetition for a certain step in a business process. In programming concepts, a multi-instance matches the for each construct: it allows to execute a certain step or even a complete subprocess for each item in a given collection, sequentially or in parallel.

A multi-instance is a regular activity that has extra properties defined (so-called multi-instance characteristics) which will cause the activity to be executed multiple times at runtime. Following activities can become a multi-instance activity:

  • User Task
  • Script Task
  • Service Task
  • Business Rule Task
  • Manual Task
  • Receive Task
  • (Embedded) Sub-Process
  • Call Activity

A Gateway or Event can not become multi-instance.

If an activity is multi-instance, this is indicated by three short lines at the bottom of that activity. Three vertical lines indicates that the instances will be executed in parallel, while three horizontal lines indicate sequential execution.

As required by the spec, each parent execution of the created executions for each instance will have following variables:

  • nrOfInstances: the total number of instances
  • nrOfActiveInstances: the number of currently active, i.e. not yet finished, instances. For a sequential multi-instance, this will always be 1
  • nrOfCompletedInstances: the number of already completed instances

These values can be retrieved by calling the execution.getVariable(x) method.

Additionally, each of the created executions will have an execution-local variable (i.e. not visible for the other executions, and not stored on process instance level) :

  • loopCounter: indicates the index in the for-each loop of that particular instance

To make an activity multi-instance, the activity xml element must have a multiInstanceLoopCharacteristics child element.

<multiInstanceLoopCharacteristics isSequential="false|true">
 ...
</multiInstanceLoopCharacteristics>

The isSequential attribute indicates if the instances of that activity are executed sequentially or parallel.

The number of instances are calculated once, when entering the activity. There are a few ways of configuring this. On way is directly specifying a number, by using the loopCardinality child element.

<multiInstanceLoopCharacteristics isSequential="false|true">
  <loopCardinality>5</loopCardinality>
</multiInstanceLoopCharacteristics>

Expressions that resolve to a positive number are also possible:

<multiInstanceLoopCharacteristics isSequential="false|true">
  <loopCardinality>${nrOfOrders-nrOfCancellations}</loopCardinality>
</multiInstanceLoopCharacteristics>

Another way to define the number of instances, is to specify the name of a process variable which is a collection using the loopDataInputRef child element. For each item in the collection, an instance will be created. Optionally, it is possible to set that specific item of the collection for the instance using the inputDataItem child element. This is shown in the following XML example:

<userTask id="miTasks" name="My Task ${loopCounter}" camunda:assignee="${assignee}">
  <multiInstanceLoopCharacteristics isSequential="false">
    <loopDataInputRef>assigneeList</loopDataInputRef>
    <inputDataItem name="assignee" />
  </multiInstanceLoopCharacteristics>
</userTask>

Suppose the variable assigneeList contains the values [kermit, gonzo, foziee]. In the snippet above, three user tasks will be created in parallel. Each of the executions will have a process variable named assignee containing one value of the collection, which is used to assign the user task in this example.

The downside of the loopDataInputRef and inputDataItem is that 1) the names are pretty hard to remember and 2) due to the BPMN 2.0 schema restrictions they can't contain expressions. We solve this by offering the collection and elementVariable attributes on the multiInstanceCharacteristics:

<userTask id="miTasks" name="My Task" camunda:assignee="${assignee}">
  <multiInstanceLoopCharacteristics isSequential="true" 
     camunda:collection="${myService.resolveUsersForTask()}" camunda:elementVariable="assignee" >
  </multiInstanceLoopCharacteristics>
</userTask>

A multi-instance activity ends when all instances are finished. However, it is possible to specify an expression that is evaluated every time one instance ends. When this expression evaluates to true, all remaining instances are destroyed and the multi-instance activity ends, continuing the process. Such an expression must be defined in the completionCondition child element.

<userTask id="miTasks" name="My Task" camunda:assignee="${assignee}">
  <multiInstanceLoopCharacteristics isSequential="false" 
     camunda:collection="assigneeList" camunda:elementVariable="assignee" >
    <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition>
  </multiInstanceLoopCharacteristics>
</userTask>

In this example, there will be parallel instances created for each element of the assigneeList collection. However, when 60% of the tasks are completed, the other tasks are deleted and the process continues.

Boundary events and multi-instance

Since a multi-instance is a regular activity, it is possible to define a boundary event on its boundary. In case of an interrupting boundary event, when the event is caught, all instances that are still active will be destroyed. Take for example following multi-instance subprocess:

Here, all instances of the subprocess will be destroyed when the timer fires, regardless of how many instances there are or which inner activities are currently not yet completed.

Loops

The loop marker is not yet natively supported by the engine. For Multiple Instance the number of repitions is known in advance - which makes it a bad candidate for loops (anyway - since it defines a completion condition that may be already sufficient for some cases).

To get around this limitation the solution is to explicitely model the loop in your bpmn process:

And be assured that we have the loop marker in our backlog to be added to the engine.

Compensation

If an activity is used for compensating the effects of another activity, it can be declared to be a compensation handler. Compensation handlers are not contained in normal flow and are only executed when a compensation event is thrown.

Notice the compensation handler icon in the bottom canter area of the "cancel hotel reservation" service task

Compensation handlers must not have incoming or outgoing sequence flows.

A compensation handler must be associated with a compensation boundary event using a directed association.

In order to declare an activity to be a compensation handler, we need to set the attribute isForCompensation to true:

<serviceTask id="undoBookHotel" isForCompensation="true" camunda:class="..." />

Additional Resources

Data-based Exclusive Gateway (XOR)

An exclusive gateway (also called the XOR gateway or more technical the exclusive data-based gateway), is used to model a decision in the process. When the execution arrives at this gateway, all outgoing sequence flow are evaluated in the order in which they are defined. The sequence flow which condition evaluates to true (or which doesn't have a condition set, conceptually having a 'true' defined on the sequence flow) is selected for continuing the process.

Note that only one sequence flow is selected when using the exclusive gateway. In case multiple sequence flow have a condition that evaluates to true, the first one defined in the XML (and only that one!) is selected for continuing the process.

If no sequence flow can be selected (no condition evaluates to true) this will result in a runtime exception unless you have a default flow defined. There can be one default flow set on the gateway itself in case no other condition matched - like an 'else' in programming languages.

Note that a gateway without an icon inside defaults to an exclusive gateway, even if we recommend to use the X within the gateway if your BPMN tool leaves the choice to you.

The XML representation of an exclusive gateway is straight-forward: one line defining the gateway and condition expressions defined on the outgoing sequence flow. The default flow (optional) is set as attribute on the gateway itself. Note that the name of the flow (used in the diagram, meant for the human being) might be different to the formal expression (used in the engine).

<exclusiveGateway id="exclusiveGw" name="Exclusive Gateway" default="flow4" />

<sequenceFlow id="flow2" sourceRef="exclusiveGw" targetRef="theTask1" name="${x==1}">
  <conditionExpression xsi:type="tFormalExpression">${x == 1}</conditionExpression>
</sequenceFlow>

<sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="theTask2" name="${x==2}">
  <conditionExpression xsi:type="tFormalExpression">${x == 2}</conditionExpression>
</sequenceFlow>

<sequenceFlow id="flow4" sourceRef="exclusiveGw" targetRef="theTask3" name="else">
</sequenceFlow>

Additional Resources

Conditional and Default Sequence Flows

A sequence flow is the connector between two elements of a process. After an element is visited during process execution, all outgoing sequence flow will be followed. This means that the default nature of BPMN 2.0 is to be parallel: two outgoing sequence flow will create two separate, parallel paths of execution.

Conditional Sequence Flow

A sequence flow can have a condition defined on it. When a BPMN 2.0 activity is left, the default behavior is to evaluate the conditions on the outgoing sequence flow. When a condition evaluates to true, that outgoing sequence flow is selected. When multiple sequence flow are selected that way, multiple executions will be generated and the process will be continued in a parallel way. Note: This is different for gateways. Gateways will handle sequence flow with conditions in specific ways, depending on the gateway type.

A conditional sequence flow is represented in XML as a regular sequence flow, containing a conditionExpression sub-element. Note that for the moment only tFormalExpressions are supported, Omitting the xsi:type="" definition will simply default to this only supported type of expressions.

<sequenceFlow id="flow" sourceRef="theStart" targetRef="theTask">
  <conditionExpression xsi:type="tFormalExpression">
    <![CDATA[${order.price > 100 && order.price < 250}]]>
  </conditionExpression>
</sequenceFlow>

Currently conditionalExpressions can only be used with UEL, detailed info about these can be found in section Expressions. The expression used should resolve to a boolean value, otherwise an exception is thrown while evaluating the condition.

The example below references data of a process variable, in the typical JavaBean style through getters.

<conditionExpression xsi:type="tFormalExpression">
  <![CDATA[${order.price > 100 && order.price < 250}]]>
</conditionExpression>

This example invokes a method that resolves to a boolean value.

<conditionExpression xsi:type="tFormalExpression">
  <![CDATA[${order.isStandardOrder()}]]>
</conditionExpression>

Default Sequence Flow

All BPMN 2.0 tasks and gateways can have a default sequence flow. This sequence flow is only selected as the outgoing sequence flow for that activity if and only if none of the other sequence flow could be selected. Conditions on a default sequence flow are always ignored.

A default sequence flow for a certain activity is defined by the default attribute on that activity. The following shows for example an exclusive gateway with a default sequence flow. Only when x is not 1 or 2, it will be chosen as outgoing sequence flow for the gateway.

Note the 'slash' marker at the beginning of the default sequence flow. The corresponding XML snippet shows how flow4 is configured as default sequence flow.

<exclusiveGateway id="exclusiveGw" name="Exclusive Gateway" default="flow4" />

<sequenceFlow id="flow2" sourceRef="exclusiveGw" targetRef="theTask1" name="${x==1}">
  <conditionExpression xsi:type="tFormalExpression">${x == 1}</conditionExpression>
</sequenceFlow>

<sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="theTask2" name="${x==2}">
  <conditionExpression xsi:type="tFormalExpression">${x == 2}</conditionExpression>
</sequenceFlow>

<sequenceFlow id="flow4" sourceRef="exclusiveGw" targetRef="theTask3" name="else">
</sequenceFlow>

Parallel Gateway

Gateways can also be used to model concurrency in a process. The most straightforward gateway to introduce concurrency in a process model, is the Parallel Gateway, which allows to fork into multiple paths of execution or join multiple incoming paths of execution.

The functionality of the parallel gateway is based on the incoming and outgoing sequence flow:

  • fork: all outgoing sequence flow are followed in parallel, creating one concurrent execution for each sequence flow.
  • join: all concurrent executions arriving at the parallel gateway wait in the gateway until an execution has arrived for each of the incoming sequence flow. Then the process continues past the joining gateway.

Note that a parallel gateway can have both fork and join behavior, if there are multiple incoming and outgoing sequence flow for the same parallel gateway. In that case, the gateway will first join all incoming sequence flow, before splitting into multiple concurrent paths of executions.

An important difference with other gateway types is that the parallel gateway does not evaluate conditions. If conditions are defined on the sequence flow connected with the parallel gateway, they are simply ignored.

Defining a parallel gateway needs one line of XML:

<parallelGateway id="myParallelGateway" />

The actual behavior (fork, join or both), is defined by the sequence flow connected to the parallel gateway.

For example, the model above comes down to the following XML:

<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="fork" />

<parallelGateway id="fork" />
<sequenceFlow sourceRef="fork" targetRef="receivePayment" />
<sequenceFlow sourceRef="fork" targetRef="shipOrder" />

<userTask id="receivePayment" name="Receive Payment" />  
<sequenceFlow sourceRef="receivePayment" targetRef="join" />

<userTask id="shipOrder" name="Ship Order" /> 
<sequenceFlow sourceRef="shipOrder" targetRef="join" />

<parallelGateway id="join" />
<sequenceFlow sourceRef="join" targetRef="archiveOrder" />

<userTask id="archiveOrder" name="Archive Order" /> 
<sequenceFlow sourceRef="archiveOrder" targetRef="theEnd" />

<endEvent id="theEnd" />

In the above example, after the process is started, two tasks will be created:

ProcessInstance pi = runtimeService.startProcessInstanceByKey("forkJoin");
TaskQuery query = taskService.createTaskQuery()
                         .processInstanceId(pi.getId())
                         .orderByTaskName()
                         .asc();

List<Task> tasks = query.list();
assertEquals(2, tasks.size());

Task task1 = tasks.get(0);
assertEquals("Receive Payment", task1.getName());
Task task2 = tasks.get(1);
assertEquals("Ship Order", task2.getName());

When these two tasks are completed, the second parallel gateway will join the two executions and since there is only one outgoing sequence flow, no concurrent paths of execution will be created, and only the Archive Order task will be active.

Note that a parallel gateway does not need to be 'balanced' (i.e. a matching number of incoming/outgoing sequence flow for corresponding parallel gateways). A parallel gateway will simply wait for all incoming sequence flow and create a concurrent path of execution for each outgoing sequence flow, not influenced by other constructs in the process model. So, the following process is legal in BPMN 2.0:

Additional Resources

Inclusive Gateway

The Inclusive Gateway can be seen as a combination of an exclusive and a parallel gateway. Like an exclusive gateway you can define conditions on outgoing sequence flows and the inclusive gateway will evaluate them. But the main difference is that the inclusive gateway can take more than one sequence flow, like the parallel gateway.

The functionality of the inclusive gateway is based on the incoming and outgoing sequence flow:

  • fork: all outgoing sequence flow conditions are evaluated and for the sequence flow conditions that evaluate to true the flows are followed in parallel, creating one concurrent execution for each sequence flow.
  • join: all concurrent executions arriving at the inclusive gateway wait in the gateway until an execution has arrived for each of the incoming sequence flows that have a process token. This is an important difference with the parallel gateway. So in other words, the inclusive gateway will only wait for the incoming sequence flows that will be executed. After the join, the process continues past the joining inclusive gateway.

Note that an inclusive gateway can have both fork and join behavior, if there are multiple incoming and outgoing sequence flow for the same inclusive gateway. In that case, the gateway will first join all incoming sequence flows that have a process token, before splitting into multiple concurrent paths of executions for the outgoing sequence flows that have a condition that evaluates to true.

Defining an inclusive gateway needs one line of XML:

<inclusiveGateway id="myInclusiveGateway" />

The actual behavior (fork, join or both), is defined by the sequence flows connected to the inclusive gateway. For example, the model above comes down to the following XML:

<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="fork" />

<inclusiveGateway id="fork" />
<sequenceFlow sourceRef="fork" targetRef="receivePayment" >
<conditionExpression xsi:type="tFormalExpression">${paymentReceived == false}</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="fork" targetRef="shipOrder" >
<conditionExpression xsi:type="tFormalExpression">${shipOrder == true}</conditionExpression>
</sequenceFlow>

<userTask id="receivePayment" name="Receive Payment" />  
<sequenceFlow sourceRef="receivePayment" targetRef="join" />

<userTask id="shipOrder" name="Ship Order" /> 
<sequenceFlow sourceRef="shipOrder" targetRef="join" />

<inclusiveGateway id="join" />
<sequenceFlow sourceRef="join" targetRef="archiveOrder" />

<userTask id="archiveOrder" name="Archive Order" /> 
<sequenceFlow sourceRef="archiveOrder" targetRef="theEnd" />

<endEvent id="theEnd" />

In the above example, after the process is started, two tasks will be created if the process variables paymentReceived == false and shipOrder == true. In case only one of these process variables equals to true only one task will be created. If no condition evaluates to true and exception is thrown. This can be prevented by specifying a default outgoing sequence flow. In the following example one task will be created, the ship order task:

HashMap<String, Object> variableMap = new HashMap<String, Object>();
variableMap.put("receivedPayment", true);
variableMap.put("shipOrder", true);

ProcessInstance pi = runtimeService.startProcessInstanceByKey("forkJoin");

TaskQuery query = taskService.createTaskQuery()
                         .processInstanceId(pi.getId())
                         .orderByTaskName()
                         .asc();

List<Task> tasks = query.list();
assertEquals(1, tasks.size());

Task task = tasks.get(0);
assertEquals("Ship Order", task.getName());

When this task is completed, the second inclusive gateway will join the two executions and since there is only one outgoing sequence flow, no concurrent paths of execution will be created, and only the Archive Order task will be active.

Note that an inclusive gateway does not need to be 'balanced' (i.e. a matching number of incoming/outgoing sequence flow for corresponding inclusive gateways). An inclusive gateway will simply wait for all incoming sequence flow and create a concurrent path of execution for each outgoing sequence flow, not influenced by other constructs in the process model.

Additional Resources

Event-based Gateway

The event-based Gateway allows to take a decision based on events. Each outgoing sequence flow of the gateway needs to be connected to an intermediate catching event. When process execution reaches an event-based Gateway, the gateway acts like a wait state: execution is suspended. In addition, for each outgoing sequence flow, an event subscription is created.

Note the sequence flows running out of an event-based Gateway are different from ordinary sequence flows. These sequence flows are never actually "executed". On the contrary, they allow the process engine to determine which events an execution arriving at an event-based Gateway needs to subscribe to. The following restrictions apply:

  • An event-based Gateway must have two or more outgoing sequence flows.
  • An event-based Gateway must only be to elements of type intermediateCatchEvent only. (Receive Tasks after an event-based Gateway are not yet supported by the engine.)
  • An intermediateCatchEvent connected to an event-based Gateway must have a single incoming sequence flow.

The following process is an example of a process with an event-based Gateway. When the execution arrives at the event-based Gateway, process execution is suspended. In addition, the process instance subscribes to the alert signal event and created a timer which fires after 10 minutes. This effectively causes the process engine to wait for ten minutes for a signal event. If the signal occurs within 10 minutes, the timer is cancelled and execution continues after the signal. If the signal is not fired, execution continues after the timer and the signal subscription is cancelled.

The correspondig xml looks like this:

<definitions>
  <signal id="alertSignal" name="alert" />
  <process id="catchSignal">
    <startEvent id="start" />

    <sequenceFlow sourceRef="start" targetRef="gw1" />

    <eventBasedGateway id="gw1" />

    <sequenceFlow sourceRef="gw1" targetRef="signalEvent" />
    <sequenceFlow sourceRef="gw1" targetRef="timerEvent" />

    <intermediateCatchEvent id="signalEvent" name="Alert">
      <signalEventDefinition signalRef="alertSignal" />
    </intermediateCatchEvent>

    <intermediateCatchEvent id="timerEvent" name="Alert">
      <timerEventDefinition>
        <timeDuration>PT10M</timeDuration>
      </timerEventDefinition>
    </intermediateCatchEvent>

    <sequenceFlow sourceRef="timerEvent" targetRef="exGw1" />
    <sequenceFlow sourceRef="signalEvent" targetRef="task" />

    <userTask id="task" name="Handle alert"/>

    <exclusiveGateway id="exGw1" />

    <sequenceFlow sourceRef="task" targetRef="exGw1" />
    <sequenceFlow sourceRef="exGw1" targetRef="end" />

    <endEvent id="end" />
  </process>
</definitions>

Additional Resources

Start Events

A start event is a trigger for starting a process instance.

The engine supports the following types of start events:

There can be at most one blank or timer start event per process definition. There can be multiple message start events.

Although according to the BPMN 2.0 specification start events are not mandatory, the engine requires at least one start event to instantiate a process.

Asynchronous Instantiation

A start event may be declared as asynchronous by camunda:async="true"

<startEvent id="startEvent" camunda:async="true" />

This will ensure that the process engine creates a process instance when the process is instantiated, but the execution of the initial activities is not done synchronously. Instead a job is created and asynchronously processed by the job executor see Asynchronous Continuations for some background.

None Events

None events are unspecified, they are called 'blank' as well. For instance a 'none' start event technically means that the trigger for starting the process instance is unspecified. This means that the engine cannot anticipate when the process instance must be started. The none start event is used when the process instance is started through the API by calling one of the startProcessInstanceBy... methods.

ProcessInstance processInstance = runtimeService.startProcessInstanceByKey('invoice');

Note: a subprocess must always have a none start event.

None End Event

A 'none' end event means that the result thrown when the event is reached is unspecified. As such, the engine will not do anything extra besides ending the current path of execution. The XML representation of a none end event is the normal end event declaration, without any sub-element (other end event types all have a sub-element declaring the type).

<endEvent id="end" name="my end event" />

Intermediate None Event (throwing)

The following process diagram shows a simple example of an intermediate none event, which is often used to indicate some state achieved in the process.

This can be a good hook to monitor some KPI's, basically by adding an execution listener

<intermediateThrowEvent id="noneEvent">
  <extensionElements>
    <camunda:executionListener class="org.camunda.bpm.engine.test.bpmn.event.IntermediateNoneEventTest$MyExecutionListener" event="start" />
  </extensionElements>
</intermediateThrowEvent>

There you can add some own code to maybe send some event to your BAM tool or DWH. The engine itself doesn't do anything in that event, it just passes through.

Message Events

Message events are events which reference a named message. A message has a name and a payload. Unlike a signal, a message event is always directed at a single receiver.

A message event definition is declared using the messageEventDefinition element. The attribute messageRef references a message element declared as a child element of the definitions root element. The following is an excerpt of a process where two message events is declared and referenced by a start event and an intermediate catching message event.

<definitions id="definitions" 
  xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  xmlns:camunda="http://activiti.org/bpmn"
  targetNamespace="Examples"
  xmlns:tns="Examples">

  <message id="newInvoice" name="newInvoiceMessage" />
  <message id="payment" name="paymentMessage" />

  <process id="invoiceProcess">  

    <startEvent id="messageStart" >
        <messageEventDefinition messageRef="newInvoice" />
    </startEvent>
    ...    
    <intermediateCatchEvent id="paymentEvt" >
        <messageEventDefinition messageRef="payment" />
    </intermediateCatchEvent>
    ...
  </process>
</definitions>

Triggering Message Events

As an embeddable process engine, the camunda engine is not concerned with the receiving part of the message. This would be environment dependent and entail platform-specific activities like connecting to a JMS (Java Messaging Service) Queue/Topic or processing a Webservice or REST request. The reception of messages is therefore something you have to implement as part of the application or infrastructure into which the process engine is embedded.

After you have received a message, you can choose whether you employ the engine's in-built correlation or explicitly deliver the message to start a new process instance or trigger a waiting execution.

Using the runtime service\'s correlation methods

The engine offers a basic correlation mechanism that will either signal an execution waiting for a specific message or instantiate a process with a matching message start event. You can choose from these methods in the runtime service:

void correlateMessage(String messageName);
void correlateMessage(String messageName, String businessKey);
void correlateMessage(String messageName, Map<String, Object> correlationKeys);
void correlateMessage(String messageName, String businessKey, Map<String, Object> processVariables);
void correlateMessage(String messageName, Map<String, Object> correlationKeys, Map<String, Object> processVariables);
void correlateMessage(String messageName, String businessKey, Map<String, Object> correlationKeys, Map<String, Object> processVariables);

The messageName identifies the message as defined in the message name attribute in the process definition xml.

Correlation is successful, if there exists a single matching entity among the following:

  • Process Definition: A process definition matches, if it can be started by a message named messageName.
  • Execution (Process Instance): An execution matches, if it is waiting for a message named messageName and its process instance matches the given businessKey and correlationKeys (if provided). The correlationKeys map is matched against the process instance variables.
Current limitation: `correlationKeys` is matched against process instance variables only. These are variables that are globally visible throughout the process instance. Accordingly, variables that are defined in the scope of a child execution (e.g. in a subprocess) are not considered for correlation.

In case of successful correlation, the correlated or newly created process instance is updated with the variables from the processVariables map.

Explicitly delivering a message

Alternatively, you can explicitly deliver a message to start a process instance or trigger a waiting execution.

If the message should trigger the start of a new process instance, choose between the following methods offered by the runtime service:

ProcessInstance startProcessInstanceByMessage(String messageName);
ProcessInstance startProcessInstanceByMessage(String messageName, Map<String, Object> processVariables);
ProcessInstance startProcessInstanceByMessage(String messageName, String businessKey, Map<String, Object> processVariables);

These methods allow starting a process instance using the referenced message.

If the message needs to be received by an existing process instance, you first have to correlate the message to a specific process instance (see next section) and then trigger the continuation of the wating execution. The runtime service offers the following methods for triggering an execution based on a message event subscription:

void messageEventReceived(String messageName, String executionId);
void messageEventReceived(String messageName, String executionId, HashMap<String, Object> processVariables);

Querying for Message Event subscriptions

The engine supports message start events and intermediate message events.

In the case of a message start event, the message event subscription is associated with a particular process definition. Such message subscriptions can be queried using a ProcessDefinitionQuery:

ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
        .messageEventSubscription("newCallCenterBooking")
        .singleResult();

Since there can only be one process definition for a specific message subscription, the query always returns zero or one results. If a process definition is updated, only the newest version of the process definition has a subscription to the message event.

In the case of an intermediate catch message event, the message event subscription is associated with a particular execution. Such message event subscriptions can be queried using a ExecutionQuery:

Execution execution = runtimeService.createExecutionQuery()
      .messageEventSubscriptionName("paymentReceived")
      .processVariableValueEquals("orderId", message.getOrderId())
      .singleResult();

Such queries are called correlation queries and usually require knowledge about the processes (in this case that there will be at most one process instance for a given orderId).

Receiving Message Events

Message Start Event

A message start event can be used to start a process instance using a named message. This effectively allows us to select the right start event from a set of alternative start events using the message name.

When deploying a process definition with one or more message start events, the following considerations apply:

  • The name of the message start event must be unique across a given process definition. A process definition must not have multiple message start events with the same name. The engine throws an exception upon deployment of a process definition such that two or more message start events reference the same message of if two or more message start events reference messages with the same message name.
  • The name of the message start event must be unique across all deployed process definitions. The engine throws an exception upon deployment of a process definition such that one or more message start events reference a message with the same name as a message start event already deployed by a different process definition.
  • Process versioning: Upon deployment of a new version of a process definition, the message subscriptions of the previous version are cancelled. This is also true for message events that are not present in the new version.

When starting a process instance, a message start event can be triggered using the following methods on the RuntimeService:

ProcessInstance startProcessInstanceByMessage(String messageName);
ProcessInstance startProcessInstanceByMessage(String messageName, Map<String, Object> processVariables);
ProcessInstance startProcessInstanceByMessage(String messageName, String businessKey, Map<String, Object< processVariables);

The messageName is the name given in the name attribute of the message element referenced by the messageRef attribute of the messageEventDefinition. The following considerations apply when starting a process instance:

  • Message start events are only supported on top-level processes. Message start events are not supported on embedded sub processes.
  • If a process definition has multiple message start events, runtimeService.startProcessInstanceByMessage(...) allows to select the appropriate start event.
  • If a process definition has multiple message start events and a single none start event, runtimeService.startProcessInstanceByKey(...) and runtimeService.startProcessInstanceById(...) starts a process instance using the none start event.
  • If a process definition has multiple message start events and no none start event, runtimeService.startProcessInstanceByKey(...) and runtimeService.startProcessInstanceById(...) throw an exception.
  • If a process definition has a single message start event, runtimeService.startProcessInstanceByKey(...) and runtimeService.startProcessInstanceById(...) start a new process instance using the message start event.
  • If a process is started from a call activity, message start event(s) are only supported if
    • in addition to the message start event(s), the process has a single none start event
    • the process has a single message start event and no other start events.

The XML representation of a message start event is the normal start event declaration with a messageEventDefinition child-element:

<definitions id="definitions" 
  xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  xmlns:camunda="http://activiti.org/bpmn"
  targetNamespace="Examples"
  xmlns:tns="Examples">

  <message id="newInvoice" name="newInvoiceMessage" />

  <process id="invoiceProcess">  

    <startEvent id="messageStart" >
        <messageEventDefinition messageRef="tns:newInvoice" />
    </startEvent>
    ...    
  </process>

</definitions>

A process can be started using one of two different messages, this is useful if the process needs alternative ways to react to different start events but eventually continues in a uniform way.

Message Intermediate Catching Event

When a token arrives at the message intermediate catching event it will wait there until a message with the proper name arrives. As already described the message must be handed into the engine via the appropriate API calls.

The following example shows different message events in a process model:

<intermediateCatchEvent id="message">
        <messageEventDefinition signalRef="newCustomerMessage" />
</intermediateCatchEvent>

Instead of the message intermediate catching event you might want to think about a Receive Task instead which can serve similar purposes, but is able to be used in combination with boundary events. Together with the message intermediate catching event you might want to use the Event-based Gateway.

Message Boundary Event

Boundary events are catching events that are attached to an activity. This means that while the activity is running, the message boundary event is listening for named message. When this is caught, two things might happen depending on the configuration of the boundary event:

  • Interrupting boundary event: The activity is interrupted and the sequence flow going out of the event is followed.
  • Non-interrupting boundary event: One token stays in the activity and an additional token is created which follows the sequence flow going out of the event.

Sending Message Events

Message Intermediate Throwing Event

Message intermediate throwing event sends a message to an external service. This event has the same behaviour as a service task.

<intermediateThrowEvent id="message">
  <messageEventDefinition camunda:class="org.camunda.bpm.MyMessageServiceDelegate" />
</intermediateThrowEvent>

Message End Event

When process execution arrives in a message end event, the current path of execution is ended and a message is sent. The message end event has the same behaviour as a service task.

<endEvent id="end">
  <messageEventDefinition camunda:class="org.camunda.bpm.MyMessageServiceDelegate" />
</endEvent>

Timer Events

Timer events are events which are triggered by defined timer. They can be used as start event, intermediate event or boundary event. Boundary events can be interrupting or not.

A timer definition must have exactly one element from the following:

  • timeDate: This format specifies fixed date in ISO 8601 format, when trigger will be fired. Example:

    <timerEventDefinition>
      <timeDate>2011-03-11T12:13:14</timeDate>
    </timerEventDefinition>
  • timeDuration: To specify how long the timer should run before it is fired, a timeDuration can be specified as sub-element of timerEventDefinition. The format used is the ISO 8601 format (as required by the BPMN 2.0 specification). Example (interval lasting 10 days):

    <timerEventDefinition>
      <timeDuration>P10D</timeDuration>
    </timerEventDefinition>
  • timeCycle: Specifies repeating interval, which can be useful for starting process periodically, or for sending multiple reminders for overdue user task. Time cycle element can be in two formats. First is the format of recurring time duration, as specified by ISO 8601 standard. Example (3 repeating intervals, each lasting 10 hours):

    <timerEventDefinition>
    <timeCycle>R3/PT10H</timeCycle>
    </timerEventDefinition>

    Additionally, you can specify time cycle using cron expressions, example below shows trigger firing every 5 minutes, starting at full hour:

    0 0/5 * * * ?

    Please see tutorial for using cron expressions.

    Note: The first symbol denotes seconds, not minutes as in normal Unix cron.

    The recurring time duration is better suited for handling relative timers, which are calculated with respect to some particular point in time (e.g. time when user task was started), while cron expressions can handle absolute timers - which is particularly useful for timer start events.

You can use expressions for the timer event definitions, by doing so you can influence the timer definition based on process variables. The process variables must contain the ISO 8601 (or cron for cycle type) string for appropriate timer type.

<boundaryEvent id="escalationTimer" cancelActivity="true" attachedToRef="firstLineSupport">
   <timerEventDefinition>
    <timeDuration>${duration}</timeDuration>
  </timerEventDefinition>
</boundaryEvent>

Note: timers are only fired when the Job Executor is enabled.

Timer Start Event

A timer start event is used to create process instance at given time. It can be used both for processes which should start only once and for processes that should start in specific time intervals.

Note: a subprocess cannot have a timer start event.

Note: start timer event is scheduled as soon as process is deployed. There is no need to call startProcessInstanceBy..., although calling start process methods is not restricted and will cause one more starting of the process at the time of startProcessInstanceBy... Invocation.

The XML representation of a timer start event is the normal start event declaration, with timer definition sub-element. The following ecample process will start 4 times, in 5 minute intervals, starting on 11th march 2011, 12:13:

<startEvent id="theStart">
    <timerEventDefinition>
        <timeCycle>R4/2011-03-11T12:13/PT5M</timeCycle>
    </timerEventDefinition>
</startEvent>

and this process will start once, on a selected date:

<startEvent id="theStart">
    <timerEventDefinition>
        <timeDate>2011-03-11T12:13:14</timeDate>
    </timerEventDefinition>
</startEvent>

Timer Intermediate Catching Event

A timer intermediate event acts as a stopwatch. When an execution arrives in catching event activity, a timer is started. When the timer fires (e.g. after a specified interval), the sequence flow going out of the timer intermediate event is followed.

A timer intermediate event is defined as a intermediate catching event. The specific type sub-element is in this case a timerEventDefinition element.

<intermediateCatchEvent id="timer">
    <timerEventDefinition>
        <timeDuration>PT5M</timeDuration>
    </timerEventDefinition>
</intermediateCatchEvent>

Timer Boundary Event

A timer boundary event acts as a stopwatch and alarm clock. When an execution arrives in the activity where the boundary event is attached to, a timer is started. When the timer fires (e.g. after a specified interval), the activity is interrupted and the sequence flow going out of the timer boundary event are followed.

There is the difference between the interrupting and non interrupting timer event. The interrupting is the default. The non-interrupting leads to the original activity is not interrupted but the activity stays there. Instead an additional executions is created and send over the outgoing transition of the event. In the XML representation, the cancelActivity attribute is set to false:

<boundaryEvent id="escalationTimer" cancelActivity="false" attachedToRef="firstLineSupport"/>
   <timerEventDefinition>
    <timeDuration>PT4H</timeDuration>
  </timerEventDefinition>
</boundaryEvent>

Note: timers are only fired when the Job Executor is enabled.

Known issue with boundary events

There is a known issue regarding concurrency when using boundary events of any type. Currently, it is not possible to have multiple outgoing sequence flow attached to a boundary event. A solution to this problem is to use one outgoing sequence flow that goes to a parallel gateway.

Error Events

Error events are events which are triggered by a defined error.

Important note: a BPMN error is meant for business errors - which is pretty different to technical exceptions. So this is different to Java exceptions - which are by default handled in their own way.

Heads up! You might want to check out the basics of Threading and Transactions first.

An error event definition references an error element. The following is an example of an error end event, referencing an error declaration:

<definitions>
  <error id="myError" errorCode="ERROR-OCCURED" name="ERROR-OCCURED"/>
  <!-- ... -->
  <process>
    <!-- ... -->
    <endEvent id="myErrorEndEvent">
      <errorEventDefinition errorRef="myError" />
    </endEvent>
  </process>
</definitions>

You can trigger this error event either by a throwing error event within your process definition or from Delegation Code, see Throwing BPMN Errors from Delegation Code.

Another possibility to define an error is the setting the type (class name) of any Java Exception as error code. Example:

<definitions>
  <error id="myException" errorCode="com.company.MyBusinessException" name="myBusinessException"/>
  ...
  <process>
    ...
    <endEvent id="myErrorEndEvent">
      <errorEventDefinition errorRef="myException" />
    </endEvent>
  </process>
</definitions>

The exception type should only used for business exceptions and not for technical exceptions in the process.

An error event handler references the same error element to declare that it catches the error.

Error Start Event

An error start event can only be used to trigger an Event Sub-Process - it cannot be used for starting a process instance. The error start event is always interrupting.

Error End Event

When process execution arrives in an error end event, the current path of execution is ended and an error is thrown. This error can caught by a matching intermediate boundary error event. In case no matching boundary error event is found, the execution semantics default to the none end event semantics.

Error Boundary Event

An intermediate catching error on the boundary of an activity, or boundary error event for short, catches errors that are thrown within the scope of the activity on which it is defined.

Defining a boundary error event makes most sense on an embedded subprocess, or a call activity, as a subprocess creates a scope for all activities inside the subprocess. Errors are thrown by error end events. Such an error will propagate its parent scopes upwards until a scope is found on which a boundary error event is defined that matches the error event definition.

When an error event is caught, the activity on which the boundary event is defined is destroyed, also destroying all current executions within (e.g. concurrent activities, nested subprocesses, etc.). Process execution continues following the outgoing sequence flow of the boundary event.

A boundary error event is defined as a typical boundary event. As with the other error events, the errorRef references an error defined outside the process element:

<definitions>
  <error id="myError" errorCode="ERROR-OCCURED" name="name of error"/>
  <!-- ... -->
  <process>
    <!-- ... -->
    <subProcess id="mySubProcess">
      <!-- ... -->
    </subProcess>
    <boundaryEvent id="catchError" attachedToRef="mySubProcess">
      <errorEventDefinition errorRef="myError"/>
    </boundaryEvent>
  </process>
</definitions>

The errorCode is used to match the errors that are caught:

  • If errorRef is omitted, the boundary error event will catch any error event, regardless of the errorCode of the error.
  • In case an errorRef is provided and it references an existing error, the boundary event will only catch errors with the same error code.

Additional Resources

Signal Events

Signal events are events which reference a named signal. A signal is an event of global scope (broadcast semantics) and is delivered to all active handlers.

The following is an example of two separate processes communicating using signals. The first process is started if an insurance policy is updated or changed. After the changes have been reviewed by a human participant, a signal event is thrown, signaling that a policy has changed:

This event can now be caught by all process instances which are interested. The following is an example of a process subscribing to the event.

Note: it is important to understand that a signal event is broadcast to all active handlers. This means in the case of the example given above, that all instances of the process catching the signal would receive the event.

Signal Event Definition

A signal event definition is declared using the signalEventDefinition element. The attribute signalRef references a signal element declared as a child element of the definitions root element. The following is an excerpt of a process where a signal event is thrown and caught by intermediate events. The signalEventDefinitions reference the 'global' signal element.

<definitions>
  <!-- declaration of the signal -->
  <signal id="alertSignal" name="alert" />

  <process id="catchSignal">
    <intermediateThrowEvent id="throwSignalEvent" name="Alert">
      <!-- signal event definition -->
      <signalEventDefinition signalRef="alertSignal" />
    </intermediateThrowEvent>
    <!-- ... -->
    <intermediateCatchEvent id="catchSignalEvent" name="On Alert">
      <!-- signal event definition -->
      <signalEventDefinition signalRef="alertSignal" />
    </intermediateCatchEvent>
    <!-- ... -->          
  </process>
</definitions>

Note: Contrary to other events like an error event, a signal is not consumed if it is caught. If you have two active signal boundary events catching the same signal event, both boundary events are triggered, event if they are part of different process instances.

Throwing Signal Events via API

A signal can either be thrown by a process instance using a bpmn construct or programmatically using java API. The following methods on the org.camunda.bpm.engine.RuntimeService can be used to throw a signal programmatically:

RuntimeService.signalEventReceived(String signalName);
RuntimeService.signalEventReceived(String signalName, String executionId);

The difference between signalEventReceived(String signalName) and signalEventReceived(String signalName, String executionId) is that the first method throws the signal globally to all subscribed handlers (broadcast semantics) and the second method delivers the signal to a specific execution only.

Note: the signal event does not perform any kind of correlation to a specific process instance. On the contrary, it is broadcast to all process instances. If you need to deliver a signal to a specific process instance only, do not use the throwing signal event but perform correlation manually and use signalEventReceived(String signalName, String executionId) using the appropriate query mechanisms.

Querying for Signal Event subscriptions

It is possible to query for all executions which have subscribed to a specific signal event:

List<Execution> executions = runtimeService.createExecutionQuery()
    .signalEventSubscriptionName("alert")
    .list();

We could then use the signalEventReceived(String signalName, String executionId) method to deliver the signal to these executions.

Catching Signal Events

A signal event can be caught by an intermediate catch signal event or a signal boundary event.

Note: contrary to other events like the boundary error event, a boundary signal event does not only catch signal events thrown from the scope it is attached to. On the contrary, a signal event has global scope (broadcast semantics) meaning that the signal can be thrown from any place, even from a different process instance.

This is straightforward in the XML:

<intermediateCatchEvent id="signal">
  <signalEventDefinition signalRef="newCustomerSignal" />
</intermediateCatchEvent>

or alternatively

<boundaryEvent id="boundary" attachedToRef="task" cancelActivity="true">       
  <signalEventDefinition signalRef="alertSignal"/>
</boundaryEvent>

Signal Intermediate Throwing Event

An intermediate throwing signal event throws a signal event for a defined signal.

The signal is a broadcast to all active handlers (i.e. all catching signal events). Signals can be published synchronous or asynchronous.

  • In the default configuration, the signal is delivered synchronously. This means that the throwing process instance waits until the signal is delivered to all catching process instances. The catching process instances are also notified in the same transaction as the throwing process instance, which means that if one of the notified instances produces a technical error (throws an exception), all involved instances fail.
  • A signal can also be delivered asynchronously. In that case it is determined which handlers are active at the time the throwing signal event is reached. For each active handler, an asynchronous notification message (Job) is stored and delivered by the JobExecutor.

A signal intermediate event is defined as a intermediate throwing event. The specific type sub-element is in this case a signalEventDefinition element.

<intermediateThrowEvent id="signal">
  <signalEventDefinition signalRef="newCustomerSignal" />
</intermediateThrowEvent>

An asynchronous signal event would look like this:

<intermediateThrowEvent id="signal">
  <signalEventDefinition signalRef="newCustomerSignal" camunda:async="true" />
</intermediateThrowEvent>

Signal End Event

A signal end event throws a signal event for a defined signal and the current path of execution is ended. It has the same behaviour like a signal intermediate throwing event.

<endEvent id="signal">
        <signalEventDefinition signalRef="newCustomerSignal" />
</endEvent>

Additional Resources

Cancel and Compensation Events

Cancel and compensation events occur in the context of the transaction subprocess. Please read that part first to understand the overall idea.

Cancel End Event

The cancel end event can only be used in combination with a transaction subprocess. When the cancel end event is reached, a cancel event is thrown which must be caught by a cancel boundary event. The cancel boundary event then cancels the transaction and triggers compensation.

Cancel Boundary Event

An attached intermediate catching cancel on the boundary of a transaction subprocess, or boundary cancel event for short, is triggered when a transaction is cancelled. When the cancel boundary event is triggered, it first interrupts all executions active in the current scope. Next, it starts compensation of all active compensation boundary events in the scope of the transaction. Compensation is performed synchronously, i.e. the boundary event waits before compensation is completed before leaving the transaction. When compensation is completed, the transaction subprocess is left using the sequence flow(s) running out of the cancel boundary event.

Note: Only a single cancel boundary event is allowed for a transaction subprocess.

Note: If the transaction subprocess hosts nested subprocesses, compensation is only triggered for subprocesses that have completed successfully.

Note: If a cancel boundary event is placed on a transaction subprocess with multi instance characteristics, if one instance triggers cancellation, the boundary event cancels all instances.

A cancel boundary event is defined as a typical boundary event:

<boundaryEvent id="boundary" attachedToRef="transaction" >       
  <cancelEventDefinition />
</boundaryEvent>

Since the cancel boundary event is always interrupting, the cancelActivity attribute is not required.

Compensation Intermediate Throwing Event

An intermediate throwing compensation event can be used to trigger compensation.

Triggering compensation: Compensation can either be triggered for a designated activity or for the scope which hosts the compensation event. Compensation is performed through execution of the compensation handler associated with an activity.

  • When compensation is thrown for an activity, the associated compensation handler is executed the same number of times the activity competed successfully.
  • If compensation is thrown for the current scope, all activities withing the current scope are compensated, which includes activities on concurrent branches.
  • Compensation is triggered hierarchically: if an activity to be compensated is a subprocess, compensation is triggered for all activities contained in the subprocess. If the subprocess has nested activities, compensation is thrown recursively. However, compensation is not propagated to the "upper levels" of the process: if compensation is triggered within a subprocess, it is not propagated to activities outside of the subprocess scope. The bpmn specification states that compensation is triggered for activities at "the same level of subprocess".
  • Compensation is performed in reverse order of execution. This means that whichever activity completed last is compensated first, etc.
  • The intermediate throwing compensation event can be used to compensate transaction subprocesses which competed successfully.

Note: If compensation is thrown within a scope which contains a subprocess and the subprocess contains activities with compensation handlers, compensation is only propagated to the subprocess if it has completed successfully when compensation is thrown. If some of the activities nested inside the subprocess have completed and have attached compensation handlers, the compensation handlers are not executed if the subprocess containing these activities is not completed yet. Consider the following example:

In this process we have two concurrent executions, one executing the embedded subprocess and one executing the "charge credit card" activity. Lets assume both executions are started and the first concurrent execution is waiting for a user to complete the "review bookings" task. The second execution performs the "charge credit card" activity and an error is thrown, which causes the "cancel reservations" event to trigger compensation. At this point the parallel subprocess is not yet completed which means that the compensation event is not propagated to the subprocess and thus the "cancel hotel reservation" compensation handler is not executed. If the user task (and thus the embedded subprocess) completes before the "cancel reservations" is performed, compensation is propagated to the embedded subprocess.

Process variables: When compensating an embedded subprocess, the execution used for executing the compensation handlers has access to the local process variables of the subprocess in the state they were in when the subprocess completed execution. To achieve this, a snapshot of the process variables associated with the scope execution (execution created for executing the subprocess) is taken. Form this, a couple of implications follow:

  • The compensation handler does not have access to variables added to concurrent executions created inside the subprocess scope.
  • Process variables associated with executions higher up in the hierarchy, (for instance process variables associated with the process instance execution are not contained in the snapshot: the compensation handler has access to these process variables in the state they are in when compensation is thrown.
  • A variable snapshot is only taken for embedded subprocesses, not for other activities.

Current limitations:

  • waitForCompletion="false" is currently unsupported. When compensation is triggered using the intermediate throwing compensation event, the event is only left, after compensation completed successfully.
  • Compensation itself is currently performed by concurrent executions. The concurrent executions are started in reverse order in which the compensated activities completed. Future versions of activity might include an option to perform compensation sequentially.
  • Compensation is not propagated to sub process instances spawned by call activities.

A compensation intermediate event is defined as a intermediate throwing event. The specific type sub-element is in this case a compensateEventDefinition element.

<intermediateThrowEvent id="throwCompensation">
  <compensateEventDefinition />
</intermediateThrowEvent>

In addition, the optional argument activityRef can be used to trigger compensation of a specific scope / activity:

<intermediateThrowEvent id="throwCompensation">
  <compensateEventDefinition activityRef="bookHotel" />
</intermediateThrowEvent>

Compensation Boundary Event

An attached intermediate catching compensation on the boundary of an activity or compensation boundary event for short, can be used to attach a compensation handler to an activity.

The compensation boundary event must reference a single compensation handler using a directed association.

A compensation boundary event has a different activation policy from other boundary events. Other boundary events like for instance the signal boundary event are activated when the activity they are attached to is started. When the activity is left, they are deactivated and the corresponding event subscription is cancelled. The compensation boundary event is different. The compensation boundary is activated when the activity is attached to completes successfully. At this point, the corresponding subscription to compensation events is created. The subscription is removed either when a compensation event is triggered or when the corresponding process instance ends. From this, it follows:

  • When compensation is triggered, the compensation handler associated with the compensation boundary event is invoked the same number of times the activity it is attached to completed successfully.
  • If a compensation boundary event is attached to an activity with multiple instance characteristics, a compensation event subscription is created for each instance.
  • If a compensation boundary event is attached to an activity which is contained inside a loop, a compensation event subscription is created for each time the activity is executed.
  • If the process instance ends, the subscriptions to compensation events are cancelled.

Note: the compensation boundary event is not supported on embedded subprocesses.

A compensation boundary event is defined as a typical boundary event:

<boundaryEvent id="compensateBookHotelEvt" attachedToRef="bookHotel" >       
  <compensateEventDefinition />
</boundaryEvent>

<association associationDirection="One" id="a1"  sourceRef="compensateBookHotelEvt" targetRef="undoBookHotel" />

<serviceTask id="undoBookHotel" isForCompensation="true" camunda:class="..." />

Since the compensation boundary event is activated after the activity has completed successfully, the cancelActivity attribute is not supported.

Additional Resources

Embedded Subprocess

A subprocess is an activity that contains other activities, gateways, events, etc. which on itself form a process that is part of the bigger process. A Subprocess is completely defined inside a parent process (that's why it's often called an embedded Subprocess).

Subprocesses have two major use cases:

  • Subprocess allow hierarchical modeling. Many modeling tools allow that subprocess can be collapsed, hiding all the details of the subprocess and displaying a high-level end-to-end overview of the business process.
  • A subprocess creates a new scope for events. Events that are thrown during execution of the subprocess, can be caught by a boundary event on the boundary of the subprocess, thus creating a scope for that event limited to the subprocess.

Using a subprocess does impose some constraints:

  • A subprocess can only have one none start event, no other start event types are allowed. A subprocess must at least have one end event. Note that the BPMN 2.0 specification allows to omit the start and end events in a subprocess, but the current engine implementation does not support this.
  • Sequence flow can not cross subprocess boundaries.

A subprocess is visualized as a typical activity, i.e. a rounded rectangle. In case the subprocess is collapsed, only the name and a plus-sign are displayed, giving a high-level overview of the process:

In case the subprocess is expanded, the steps of the subprocess are displayed within the subprocess boundaries:

One of the main reasons to use a subprocess, is to define a scope for a certain event. The following process model shows this: both the investigate software/investigate hardware tasks need to be done in parallel, but both tasks need to be done within a certain time, before Level 2 support is consulted. Here, the scope of the timer (i.e. which activities must be done in time) is constrained by the subprocess.

A subprocess is defined by the subprocess element. All activities, gateways, events, etc. that are part of the subprocess, need to be enclosed within this element.

<startEvent id="outerStartEvent" />
<!-- ... other elements ... -->
<subProcess id="subProcess">
   <startEvent id="subProcessStart" />
   <!-- ... other subprocess elements ... -->
   <endEvent id="subProcessEnd" />
</subProcess>

Additional Resources

Call Activity

BPMN 2.0 makes a distinction between an embedded subprocess and the call activity. From a conceptual point of view, both will call a subprocess when process execution arrives at the activity.

The difference is that the call activity references a process that is external to the process definition, whereas the subprocess is embedded within the original process definition. The main use case for the call activity is to have a reusable process definition that can be called from multiple other process definitions.

When process execution arrives in the call activity, a new process instance is created, which is used to execute the subprocess, potentially creating parallel child execution as within a regular process. The super process instance waits until the subprocess is completely ended, and continues the original process afterwards.

A call activity is visualized the same as a collapsed embedded subprocess, however with a thick border. Depending on the modeling tool, a call activity can also be expanded, but the default visualization is the collapsed representation.

A call activity is a regular activity, that requires a calledElement that references a process definition by its key. In practice, this means that the id of the process is used in the calledElement:

<callActivity id="callCheckCreditProcess" name="Check credit" calledElement="checkCreditProcess" />

Note that the process definition of the subprocess is resolved at runtime. This means that the subprocess can be deployed independently from the calling process, if needed.

CalledElement Binding

In a call activity contains the calledElement attribute the process definition key as reference to the subprocess. This means that always the latest process definition version of the subprocess is called. To call another version of the subprocess it is possible to define the attribute calledElementBinding and calledElementVersion in the call activity. Both attributes are optional.

CalledElementBinding has three different values:

  • latest: always call latest process definition version (the same behaviour without this attribute)
  • deployment: if called process definition is part of the same deployment as the calling process definition use version from deployment
  • version: call fix version of process definition, calledElementVersion is required
<callActivity id="callSubProcess" calledElement="checkCreditProcess"
  camunda:calledElementBinding="latest|deployment|version"
  camunda:calledElementVersion="17">
</callActivity>

Passing variables

You can pass process variables to the subprocess and vice versa. The data is copied into the subprocess when it is started and copied back into the main process when it ends.

<callActivity id="callSubProcess" calledElement="checkCreditProcess" >
  <extensionElements>
    <camunda:in source="someVariableInMainProcess" target="nameOfVariableInSubProcess" />
    <camunda:out source="someVariableInSubProcss" target="nameOfVariableInMainProcess" />
  </extensionElements>
</callActivity>

Furthermore, you can configure the call activity, that all process variables are passed to the subprocess and vice versa. The process variables have the same name in the parent process as in the subprocess.

<callActivity id="callSubProcess" calledElement="checkCreditProcess" >
  <extensionElements>
    <camunda:in variables="all" />
    <camunda:out variables="all" />
  </extensionElements>
</callActivity>

We use a Custom Extension as a shortcut for the BPMN standard elements called dataInputAssociation and dataOutputAssociation, which only work if you declare process variables in the BPMN 2.0 standard way.

It is possible to use expressions here as well:

<callActivity id="callSubProcess" calledElement="checkCreditProcess" >
  <extensionElements>
    <camunda:in sourceExpression="${x+5}" target="y" />
    <camunda:out sourceExpression="${y+5}" target="z" />
  </extensionElements>
</callActivity>

So in the end z = y+5 = x+5+5

Passing Business Key

You can pass the business key to the subprocess. The data is copied into the subprocess when it is started. You can not give back the business key to the parent process because the business key is not changeable.

<callActivity id="callSubProcess" calledElement="checkCreditProcess" >
  <extensionElements>
    <camunda:in businessKey="#{execution.processBusinessKey}" />
  </extensionElements>
</callActivity>

Example

The following process diagram shows a simple handling of an order. Since for example the billing could be common to many other processes, it is modeled as a call activity.

The XML looks as follows:

<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="shipping" />

<callActivity id="shipping" name="Shipping" calledElement="shippingProcess" />
<sequenceFlow id="flow2" sourceRef="shipping" targetRef="billing" />

<callActivity id="billing" name="Billing" calledElement="billingProcess" />
<sequenceFlow id="flow3" sourceRef="billing" targetRef="end" />

<endEvent id="end" />

There is nothing special to the process definition of the subprocess. It could as well be used without being called from another process.

Additional Resources

Event Subprocess

The Event subprocess is a subprocess that is triggered by an event. An Event subprocess can be added at the process level or at any subprocess level. The event used to trigger an event subprocess is configured using a start event. From this, it follows that none start events are not supported for Event subprocesses. An Event subprocess might be triggered using events like message events, error events, signal events, timer events, or compensation events. The subscription to the start event is created when the scope (process instance or subprocess) hosting the Event subprocess is created. The subscription is removed when the scope is destroyed.

An Event subprocess may be interrupting or non-interrupting. An interrupting subprocess cancels any executions in the current scope. A non-interrupting Event subprocess spawns a new concurrent execution. While an interrupting Event subprocess can only be triggered once for each activation of the scope hosting it, a non-interrupting Event subprocess can be triggered multiple times. The fact whether the subprocess is interrupting is configured using the start event triggering the Event subprocess.

An Event subprocess must not have any incoming or outgoing sequence flows. Since an Event subprocess is triggered by an event, an incoming sequence flow makes no sense. When an Event subprocess is ended, either the current scope is ended (in case of an interrupting Event subprocess), or the concurrent execution spawned for the non-interrupting subprocess is ended.

The Event subprocess is visualized as a an embedded subprocess with a dotted outline.

It is represented using XML in the same way as a an embedded subprocess. In addition the attribute triggeredByEvent must have the value true:

<subProcess id="eventSubProcess" triggeredByEvent="true">
  <!-- ... -->
</subProcess>
Current limitations:
  • Only interrupting Event subprocesses are supported.
  • Only Event subprocess triggered using an Error Start Event or Message Start Event are supported.

Example

The following is an example of an Event subprocess triggered using an Error Start Event. The Event subprocess is located at the "process level", i.e. is scoped to the process instance:

This is how the Event subprocess looks like in XML:

<subProcess id="eventSubProcess" triggeredByEvent="true">
  <startEvent id="catchError">
    <errorEventDefinition errorRef="error" /> 
  </startEvent>
  <sequenceFlow id="flow2" sourceRef="catchError" targetRef="taskAfterErrorCatch" />
  <userTask id="taskAfterErrorCatch" name="Provide additional data" />
</subProcess>

As already stated, an Event subprocess can also be added to an embedded subprocess. If it is added to an embedded subprocess, it becomes an alternative to a boundary event. Consider the two following process diagrams. In both cases the embedded subprocess throws an error event. Both times the error is caught and handled using a user task.

as opposed to:

In both cases the same tasks are executed. However, there are differences between both modelling alternatives:

  • The embedded subprocess is executed using the same execution which executed the scope it is hosted in. This means that an embedded subprocess has access to the variables local to it's scope. When using a boundary event, the execution created for executing the embedded subprocess is deleted by the sequence flow leaving the boundary event. This means that the variables created by the embedded subprocess are not available anymore.
  • When using an Event subprocess, the event is completely handled by the subprocess it is added to. When using a boundary event, the event is handled by the parent process.

These two differences can help you decide whether a boundary event or an embedded subprocess is better suited for solving a particular process modeling / implementation problem.

Additional Resources

Transaction Subprocess

A transaction subprocess is an embedded subprocess, which can be used to group multiple activities to a transaction. A transaction is a logical unit of work which allows to group a set of individual activities, such that they either succeed or fail collectively.

Possible outcomes of a transaction: A transaction can have three different outcomes:

  • A transaction is successful, if it is neither cancelled not terminated by a hazard. If a transaction subprocess is successful, it is left using the outgoing sequenceflow(s). A successful transaction might be compensated if a compensation event is thrown later in the process.

    Note: just as "ordinary" embedded subprocesses, a transaction may be compensated after successful completion using an intermediary throwing compensation event.

  • A transaction is cancelled, if an execution reaches the cancel end event. In that case, all executions are terminated and removed. A single remaining execution is then set to the cancel boundary event, which triggers compensation. After compensation is completed, the transaction subprocess is left using the outgoing sequence flow(s) of the cancel boundary event.

  • A transaction is ended by a hazard, if an error event is thrown, that is not caught within the scope of the transaction subprocess. (This also applies if the error is caught on the boundary of the transaction subprocess.) In this case, compensation is not performed.

The following diagram illustrates the three different outcomes:

A transaction subprocess is represented using xml using the transaction element:

<transaction id="myTransaction" >
  <!-- ... -->
</transaction>
Relation to ACID transactions: It is important not to confuse the bpmn transaction subprocess with technical (ACID) transactions. The bpmn transaction subprocess is not a way to scope technical transactions. In order to understand transaction management in the, read the section on concurrency and transactions.

A bpmn transaction is different from a technical transaction in the following ways:

  • While an ACID transaction is typically short lived, a bpmn transaction may take hours, days or even months to complete. (Consider the case where one of the activities grouped by a transaction is a usertask, typically people have longer response times than applications. Or, in another situation, a bpmn transaction might wait for some business event to occur, like the fact that a particular order has been fulfilled.) Such operations usually take considerably longer to complete than updating a record in a database, or storing a message using a transactional queue.

  • Because it is impossible to scope a technical transaction to the duration of a business activity, a bpmn transaction typically spans multiple ACID transactions.

  • Since a bpmn transaction spans multiple ACID transactions, we loose ACID properties. For example, consider the example given above. Let's assume the "book hotel" and the "charge credit card" operations are performed in separate ACID transactions. Let's also assume that the "book hotel" activity is successful. Now we have an intermediary inconsistent state, because we have performed an hotel booking but have not yet charged the credit card. Now, in an ACID transaction, we would also perform different operations sequentially and thus also have an intermediary inconsistent state. What is different here, is that the inconsistent state is visible outside of the scope of the transaction. For example, if the reservations are made using an external booking service, other parties using the same booking service might already see that the hotel is booked. This means, that when implementing business transactions, we completely loose the isolation property (Granted: we usually also relax isolation when working with ACID transactions to allow for higher levels of concurrency, but there we have fine grained control and intermediary inconsistencies are only present for very short periods of times).

  • A bpmn business transaction can also not be rolled back in the traditional sense. Since it spans multiple ACID transactions, some of these ACID transactions might already be committed at the time the bpmn transaction is cancelled. At this point, they cannot be rolled back anymore.

Since bpmn transactions are long-running in nature, the lack of isolation and a rollback mechanism need to be dealt with differently. In practice, there is usually no better solution than to deal with these problems in a domain specific way:

  • The rollback is performed using compensation. If a cancel event is thrown in the scope of a transaction, the effects of all activities that executed successfully and have a compensation handler are compensated.
  • The lack of isolation is also often dealt with using domain specific solutions. For instance, in the example above, an hotel room might appear to be booked to a second customer, before we have actually made sure that the first customer can pay for it. Since this might be undesirable from a business perspective, a booking service might choose to allow for a certain amount of overbooking.
  • In addition, since the transaction can be aborted in case of a hazard, the booking service has to deal with the situation where a hotel room is booked but payment is never attempted (since the transaction was aborted). In that case the booking service might choose a strategy where a hotel room is reserved for a maximum period of time and if payment is not received until then, the booking is cancelled.

To sum it up: while ACID transactions offer a generic solution to such problems (rollback, isolation levels and heuristic outcomes), we need to find domain specific solutions to these problems when implementing business transactions.

Current limitations: The bpmn specification requires that the process engine reacts to events issued by the underlying transaction protocol and for instance that a transaction is cancelled, if a cancel event occurs in the underlying protocol. As an embeddable engine, the camunda engine does currently not support this. (For some ramifications of this, see paragraph on consistency below.)

Consideristency on top of ACID transactions and optimistic concurrency: A bpmn transaction guarantees consistency in the sense that either all activities compete successfully, or if some activity cannot be performed, the effects of all other successful activities are compensated. So either way we end up in a consistent state. However, it is important to recognize that in camundaBPM, the consistency model for bpmn transactions is superposed on top of the consistency model for process execution. The camunda engine executes processes in a transactional way. Concurrency is addressed using optimistic locking. In the engine, bpmn error, cancel and compensation events are built on top of the same acid transactions and optimistic locking. For example, a cancel end event can only trigger compensation if it is actually reached. It is not reached if some undeclared exception is thrown by a service task before. Or, the effects of a compensation handler can not be committed if some other participant in the underlying ACID transaction sets the transaction to the state rollback-only. Or, when two concurrent executions reach a cancel end event, compensation might be triggered twice and fail with an optimistic locking exception. All of this is to say that when implementing bpmn transactions in the core engine, the same set of rules apply as when implementing "ordinary" processes and subprocesses. So to effectively guarantee consistency, it is important to implement processes in a way that does take the optimistic, transactional execution model into consideration.

Additional Resources