Skip to main content

Gateways

Gateways control how tokens split and merge in a process. Each gateway type has its own rules for which outgoing flows fire (split) and what it takes to advance (join).

Common attributes

AttributeNotes
idRequired
nameOptional
defaultFlow ID — fallback flow taken when no condition matches (exclusive and inclusive only)
gatewayDirectionInformational, not enforced

A default flow must be one of the gateway's outgoing flows and must not carry a conditionExpression.


Exclusive gateway (XOR)

Picks exactly one outgoing flow at a split. The first flow whose condition evaluates to true is taken, all others receive a "dead" token (the path is skipped). When used as a join, it's a pass-through merge — no synchronisation.

Behavior

ScenarioBehavior
Split with conditionsFlows are evaluated in document order, first true wins
Split with no match and a defaultDefault flow is taken
Split with no match and no defaultRuntime error
JoinPass-through — every incoming token advances

Example

<bpmn:exclusiveGateway id="gw-route" name="Route?" default="flow-default">
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>flow-vip</bpmn:outgoing>
<bpmn:outgoing>flow-default</bpmn:outgoing>
</bpmn:exclusiveGateway>

<bpmn:sequenceFlow id="flow-vip" name="VIP" sourceRef="gw-route" targetRef="task-vip">
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=tier = "vip"</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<bpmn:sequenceFlow id="flow-default" name="Standard" sourceRef="gw-route" targetRef="task-standard" />

Validation

CheckSeverity
default doesn't match an outgoing flowError
Default flow carries a conditionExpressionError
Non-default outgoing flow has no condition (when default is set)Warning
Single outgoing flow has no condition and no defaultWarning

Parallel gateway (AND)

Splits into all outgoing flows simultaneously and joins by waiting for a token on every incoming flow.

Behavior

ScenarioBehavior
SplitAll outgoing flows are activated. No condition evaluation
JoinWaits until a live token has arrived on every incoming flow, then activates a single outgoing token

Conditions on outgoing flows are ignored (with a warning at deployment).

Example

<bpmn:parallelGateway id="fork" name="Split">
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
<bpmn:outgoing>f3</bpmn:outgoing>
</bpmn:parallelGateway>

<!-- ... tasks in parallel branches ... -->

<bpmn:parallelGateway id="join" name="Join">
<bpmn:incoming>f2-done</bpmn:incoming>
<bpmn:incoming>f3-done</bpmn:incoming>
<bpmn:outgoing>f4</bpmn:outgoing>
</bpmn:parallelGateway>

Validation

CheckSeverity
Outgoing flow carries a conditionExpressionWarning (condition is ignored)

Inclusive gateway (OR)

Activates every outgoing flow whose condition evaluates to true. The matching join waits for all of those branches to arrive.

Behavior

ScenarioBehavior
SplitAll matching conditions activate their flows in parallel, if none match and a default is set, only the default fires, if none match and no default, runtime error
JoinWaits for every branch the matching split activated, merges branch variables into the parent scope before advancing

Example

<bpmn:inclusiveGateway id="gw-or" name="Select Channels" default="flow-email">
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>flow-sms</bpmn:outgoing>
<bpmn:outgoing>flow-push</bpmn:outgoing>
<bpmn:outgoing>flow-email</bpmn:outgoing>
</bpmn:inclusiveGateway>

<bpmn:sequenceFlow id="flow-sms" sourceRef="gw-or" targetRef="send-sms">
<bpmn:conditionExpression>=smsEnabled</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<bpmn:sequenceFlow id="flow-push" sourceRef="gw-or" targetRef="send-push">
<bpmn:conditionExpression>=pushEnabled</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<bpmn:sequenceFlow id="flow-email" sourceRef="gw-or" targetRef="send-email" />

Validation

CheckSeverity
default doesn't match an outgoing flowError
Default flow carries a conditionExpressionError
Non-default outgoing flow has no condition (when default is set)Warning

Event-based gateway

Waits for the first of several events to fire and then takes the corresponding branch. Each outgoing flow must lead directly to an intermediate catch event with a message, timer, or signal trigger.

Attributes

AttributeDefaultNotes
instantiatefalseNot supported. Deployment fails with an error if set to true
eventGatewayTypeExclusiveOnly Exclusive (first event wins) is implemented. Parallel deploys without error but is treated as Exclusive at runtime

Behavior

ScenarioBehavior
First event firesThe winning branch advances, all other branches are cancelled
No event firesToken waits indefinitely (use an attached timer to bound the wait)

To express "every event that fires advances its own branch", use a parallel gateway followed by individual catch events instead of an event-based gateway in parallel mode.

Example

<bpmn:eventBasedGateway id="gw-event" name="Wait for First">
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>flow-timer</bpmn:outgoing>
<bpmn:outgoing>flow-msg</bpmn:outgoing>
</bpmn:eventBasedGateway>

<bpmn:sequenceFlow id="flow-timer" sourceRef="gw-event" targetRef="catch-timer" />
<bpmn:intermediateCatchEvent id="catch-timer" name="30s Timeout">
<bpmn:incoming>flow-timer</bpmn:incoming>
<bpmn:outgoing>f-timeout</bpmn:outgoing>
<bpmn:timerEventDefinition>
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">PT30S</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:intermediateCatchEvent>

<bpmn:sequenceFlow id="flow-msg" sourceRef="gw-event" targetRef="catch-msg" />
<bpmn:intermediateCatchEvent id="catch-msg" name="Confirmation Received">
<bpmn:incoming>flow-msg</bpmn:incoming>
<bpmn:outgoing>f-confirmed</bpmn:outgoing>
<bpmn:messageEventDefinition messageRef="Msg_Confirm" />
</bpmn:intermediateCatchEvent>

Validation

CheckSeverity
Fewer than 2 outgoing flowsError
No incoming flowError
instantiate="true" set on the gatewayError
Outgoing flow leads to a node that isn't an intermediate catch eventError
Catch event trigger isn't message, signal, or timerError
Outgoing flow carries a conditionExpressionError

FAQ

What's the difference between exclusive, parallel, and inclusive gateways in BPMN?

An exclusive (XOR) gateway takes exactly one outgoing flow at a split — the first whose condition returns true, or the default if none match. A parallel (AND) gateway takes every outgoing flow unconditionally, and at a join waits until a token has arrived on every incoming branch. An inclusive (OR) gateway takes every outgoing flow whose condition is true (or the default if none match), and its matching join waits until at least one branch has arrived and no other live token in scope can still reach it.

What happens at an exclusive gateway when no outgoing condition matches?

If the gateway has a default flow configured, the default flow is taken. If no default exists, the process fails at runtime with a routing error. Conditions are evaluated in the order the outgoing flows appear in the BPMN XML, and the first to return true wins.

Can a default flow have a condition expression?

No. A default flow must not carry a conditionExpression. The modeler and the engine both reject this at deployment with a validation error. The same rule applies to default flows on exclusive gateways, inclusive gateways, and on activities with multiple outgoing flows.

What happens if a parallel gateway join receives a token on only some of its incoming flows?

The join blocks. A parallel join waits until a live token has arrived on every incoming sequence flow, then fires exactly once. If one branch never produces a token — for example because an upstream task failed and was not compensated — the join waits indefinitely. Use a timer boundary event on the surrounding scope to bound the wait.

How does an inclusive (OR) join decide when to fire?

The inclusive join fires as soon as at least one branch has arrived AND no other live token elsewhere in the enclosing scope can still reach the join without first passing through it. This reachability check (Völzer-Vanhatalo enablement) handles loops, nested gateways, and asymmetric splits correctly. The join does not need to know which split forked the incoming tokens, and tokens parked in disjoint fragments of the process don't keep it waiting.

What can come after an event-based gateway?

Each outgoing flow must lead directly to an intermediate catch event with a message, signal, or timer trigger. Tasks, gateways, and any other element types are rejected at deployment. Conditional, multiple, and parallel-multiple catch events are also rejected — only message, signal, and timer are wired into the gateway's race selector.

Can an event-based gateway start a process instance (`instantiate="true"`)?

No. The instantiating event gateway from the BPMN 2.0 specification is not supported. Deployment fails with an explicit validation error if the instantiate attribute is set to true. To trigger a new process instance from an event, use a message start event or a signal start event on the process itself.

Does QuantumBPM support the BPMN complex gateway?

No. The complex gateway is rejected at deployment by both the modeler and the engine. Most use cases for the complex gateway — for example, "fire when 2 of 3 branches arrive" — can be expressed with an exclusive or inclusive gateway combined with FEEL conditions on the outgoing flows, or by adding a script task that computes the routing decision explicitly.

Can an exclusive gateway be used as a join to merge multiple branches?

Yes. Used as a join, an exclusive gateway is a simple pass-through merge: every incoming token advances independently with no synchronisation. This is the standard way to reconverge alternative branches (originally split by an XOR gateway) onto a common downstream path without waiting for the other branches.