BPMN: Beyond the Basics
This publication begins a series of articles about the hidden nuances and pitfalls of BPMN 2.0. Despite the widespread belief that the notation is intuitive, in practice, its implementation in a process engine can present several surprises. Analysts have it a bit easier: if something is unclear in the model, it can be written in comments or explained to the reader in words. Developers don't have that luxury – the engine requires precise instructions with zero ambiguity. Therefore, it’s important to understand exactly how each element works.
Let’s start with the simplest one – the Exclusive Gateway.
At first glance, everything seems obvious: you place a diamond shape, draw arrows, and voilà! But what happens inside the engine? How does it choose the execution path? What if multiple conditions are met simultaneously? And supposing none of the conditions are fulfilled? In this article, we will address these questions and look at the specifics of implementing and using this element.
We will examine these using examples from Jmix BPM with the Flowable engine. Still, the principles are universal – BPMN 2.0 notation is consistent, and the core mechanisms for element operation are similar across all engines.
A Familiar Concept, But Not Quite What You Think
Everyone has dealt with flowcharts of algorithms. As you may recall, a diamond shape in a flowchart represents a conditional IF statement, with only two outcomes: TRUE or FALSE. It seems that this is why analysts and developers, when placing an exclusive gateway on diagrams, automatically phrase questions so that the answers are "yes" or "no."
This is a mistake! Because of this school habit, BPMN models often become unnecessarily bulky, as authors try to model decision-making strictly within the "yes-no" logic.
In reality, an Exclusive Gateway is more like a SWITCH statement than an IF statement; it can have multiple outgoing paths:
Here’s how an Exclusive Gateway would look in code:
switch (forecast) {
case "rain" -> advise = "Take an umbrella";
case "snow" -> advise = "Wear warm clothes";
case "sunny" -> advise = "Wear sunglasses and sunscreen";
case "windy" -> advise = "Hold onto your hat";
case "storm" -> advise = "Stay indoors if possible";
default -> advise = "Check the forecast for better advice";
}
There can be as many outgoing paths as needed – even 100 or more. this might make the diagram harder to read, the engine handles it effortlessly! The process can function without any graphical representation at all.
(Yes, that’s possible. You simply remove the section in the XML file that starts with the <bpmndi:BPMNDiagram id="BPMNDiagram_process">
tag.)
Let’s continue with our previous example. If we follow the IF-ELSE pattern, the question about rain with three possible answers would turn into this construction:
This seems a little bit complicated, doesn’t it? When looking at such a diagram, it's easy to lose focus due to the identical answers. The first 'Yes' means it will rain, while the second 'Yes' means it definitely won’t. Meanwhile, 'No' indicates that it's uncertain, so rain might still happen.
You can avoid the question entirely. Simply write clear continuation options on the outgoing flows, and you won’t have to sound like a person asking, “Could you please repeat the question?” – otherwise, it can be hard to understand the answer. For example, like this:
Those who divide, divide; those who unite, unite
The purpose of any gateway is not to ask questions but to manage the branching of the process. In other words, it allows the process to split into multiple paths, which may later converge – or not, as is sometimes the case.
Exclusive Gateways can serve as 'fork' or 'join' types in the process. A good practice is not mixing these roles, so let each gateway focus on one task. Unfortunately, it is quite common to see a single gateway performing both roles:
Never do this! A gateway should only have one role. Don’t be greedy –place more gateways so it’s clear what each one is responsible for.
Unfortunately, the notation doesn’t prohibit a gateway from having multiple incoming and outgoing flows. So even a complex structure like the one below will still work:
Is a ‘join’ gateway required after a ‘fork’ one? Not at all. The flow can simply end the process here if a condition is not met. There’s no need to drag an arrow across the entire diagram just to reach an end event.
Can a gateway have only one outgoing flow? The notation does not forbid it, such a process will deploy and run fine. However, there’s no reason to do this, unless you are still working on the process and know that you will need a fork at this point but haven’t yet decided exactly how.
What’s Inside It?
Inside an Exclusive Gateway, there is nothing. That is, no properties or parameters can be set or configured. Nothing – just an ID and a name. When analysts write their questions above the diamond shapes on gateways, they essentially assign a text label to the element that has no effect on the process flow.
For example, here:
In the BPMN XML file, our gateway will look like this:
<exclusiveGateway id="Gateway_02z3qk1" name="Will it rain today?"/>
About Conditions
Conditions are defined in the properties of the arrow, and they are not visible on the diagram:
The result of evaluating a condition must be a boolean value (‘true’ or ‘false’), otherwise an error will occur.
${orderAmount > 1000}
${price > 100 && price <= 500}
${accountant.username == "jane"}
How complex can a condition in a gateway be? Actually, the simpler, the better. However, if the logic is complex, it’s more efficient to package it in code and call the corresponding method in the condition like this:
${weatherService.getForecastCondition()}
How Does it Work?
The gateway doesn’t do anything – it has no internal logic. All the logic happens on the outgoing arrows (or, formally, the sequence flows). But don’t confuse the text label visible on the diagram with the condition checked by the engine.
When the process reaches the gateway, it looks at the outgoing flows and evaluates their conditions. Every textbook says the first condition that evaluates as true will determine the path the process takes.
Is it trivial? But what does ‘first’ mean? After all, the flows don’t have numbers.
So how can we know?
Let’s take a look at the XML file:
<exclusiveGateway id="Gateway_18tv2av" name="Will it rain today?">
<incoming>Flow_0hpfy8t</incoming>
<outgoing>Flow_1dxinb9</outgoing> (1)
<outgoing>Flow_1k36sg0</outgoing> (2)
</exclusiveGateway>
Ah, now it’s clear! The first outgoing will be Flow_1dxinb9
, and the second will be Flow_1k36sg0
. But how do we figure out which is which?
Actually, it works differently. When the process reaches the gateway, the engine reads the list of outgoing flow IDs and starts looking for their definitions. The flow that is encountered first will be considered the first one.
In fact, the flow definitions can be anywhere in the XML file, so there’s no requirement that they immediately follow the gateway. The elements in the model are arranged in the order they were created. When you edit a process intensively, adding and removing blocks and arrows, the XML becomes quite mixed up. But the engine doesn’t mind.
Let’s return to our Exclusive Gateway and find the definitions of its flows. Here’s a life hack: if you give BPMN elements meaningful IDs, the model will be easier to read, and working with logs will be much simpler.
For example:
<exclusiveGateway id="Gateway_is_rain" name="Will it rain today?">
<incoming>Flow_incoming</incoming>
<outgoing>Flow_rain</outgoing>
<outgoing>Flow_clear</outgoing>
</exclusiveGateway>
<sequenceFlow id="Flow_incoming" sourceRef="startEvent1" targetRef="Gateway_is_rain"/>
<sequenceFlow id="Flow_rain" name="Yes" sourceRef="Gateway_is_rain" targetRef="Activity_script_1">
<extensionElements>
<jmix:conditionDetails conditionSource="expression"/>
</extensionElements>
<conditionExpression xsi:type="tFormalExpression">${forecast == "rain"}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="Flow_clear" name="No" sourceRef="Gateway_is_rain" targetRef="Activity_script_2"/>
Now it’s clear who’s first and who’s second. If we swap it, the processing order will change – the second will become the first and the first the second.
<exclusiveGateway id="Gateway_is_rain" name="Will it rain today?">
<incoming>Flow_incoming</incoming>
<outgoing>Flow_clear</outgoing>
<outgoing>Flow_rain</outgoing>
</exclusiveGateway>
<sequenceFlow id="Flow_incoming" sourceRef="startEvent1" targetRef="Gateway_is_rain" />
<sequenceFlow id="Flow_clear" name="Нет" sourceRef="Gateway_is_rain" targetRef="Activity_1odfau0"> (1)
<extensionElements>
<jmix:conditionDetails conditionSource="expression" />
</extensionElements>
</sequenceFlow>
<sequenceFlow id="Flow_rain" name="Yes" sourceRef="Gateway_is_rain" targetRef="Activity_0agmpfb"> (2)
<extensionElements>
<jmix:conditionDetails conditionSource="expression" />
</extensionElements>
<conditionExpression xsi:type="tFormalExpression">${forecast == "rain"}</conditionExpression>
</sequenceFlow>
As seen in this example, while BPMN is a graphical notation, there are situations where you need to look at the code, as the diagrams cannot always be interpreted unambiguously.
Empty Condition Means "Yes."
You probably noticed that no condition is specified for the second outgoing flow. What do you think will happen in this case?
If no condition is specified, it is considered true. In other words, when we swap the flow definitions, the engine won't even calculate the expression ${forecast == 'rain'}
it will simply follow the path labeled ’Flow_clear’
.
The developers often forget to set conditions on the outgoing flows from an Exclusive Gateway, causing the process to behave oddly – constantly veering in the wrong direction, even though the variable values are correct and logically it should be otherwise. Unfortunately, BPM engines like Camunda and Flowable do not check if conditions are set on outgoing flows during process deployment, so it can be hard to catch this error.
Multiple Truths Are Possible
Can multiple conditions be true at the same time? Clearly, yes. This follows directly from what we discussed earlier about the order of processing. The engine evaluates them sequentially, and the conditions are not related to each other. But only one will trigger.
However, since it's difficult to control this order in practice, you'd have to check the XML each time, so it's better to make the conditions mutually exclusive.
If you need to implement logic where multiple options could truly be triggered, you might want to consider an Inclusive Gateway, that handles this scenario.
It’s also worth considering DMN decision tables, where you can implement almost any logic for choosing the appropriate option, even without writing code.
In general, DMN is a powerful and greatly underappreciated tool, which we will discuss separately.
Default Flow
Of course, you know that it’s recommended to assign one of the outgoing flows as the default flow. Because if none of the conditions are true, the engine will throw an exception, and the process will stop. Therefore, it’s better to direct the process somewhere rather than having it fail due to an error.
By the way, what happens if a condition is set on the default flow? Absolutely nothing, the engine will ignore it.
On diagrams, the default flow is represented with a forward slash.
In general, it’s exactly the same as with the switch statement.
Avoid Cascades
Sometimes, there are several questions. And then, the urge is to put multiple gateways. But don’t rush! Take a close look – if there are no real splits on this path but rather just refining the answer by filtering out invalid options, you don’t need to create a cascade of exclusive gateways. Instead, you can write a more complex condition.
Here we see to gateways for evaluating atomic conditions Sky == blue
and Sea == green
. (To be a pedant, you’d write sky.color == 'blue'
, but don’t care.) If both of them are true, the process goes further. But the model looks complicated.
As, for instance, here:
Let’s summarize:
-
An Exclusive Gateway contains no internal logic.
-
Its name is just for better understanding by humans and does not affect the engine.
-
The Exclusive Gateway can have as many outgoing flows as needed. It’s more like a SWITCH statement than an IF statement.
-
Avoid cascading Exclusive Gateways – formulate comprehensive conditions to avoid asking too many questions.
-
Assign one outgoing flow as the default flow.
-
Transition conditions are in the properties of outgoing flows and are not formally related.
-
The engine does not check whether the conditions are mutually exclusive; they can be identical. This is the developer’s responsibility.
-
The engine checks the conditions in the order in which the outgoing flows are listed in the XML file.
-
As soon as one condition is true, the process moves along that branch, and the other conditions will not be checked.
-
If no conditions are true, the default flow will be selected.
That’s all for now! Now you’ve definitely got to know the Exclusive Gateway. Other BPMN elements also have their surprises, and we’ll continue to explore them in future articles.