Skip to main content

Exclusive Gateway (XOR)

The Exclusive Gateway (also known as XOR Gateway) makes either/or decisions by choosing exactly ONE path from multiple options based on conditions.

Symbol​

In BPMN diagrams, it's shown as a diamond with an "X" marker or empty diamond.

When to Use​

Use Exclusive Gateways when you need to:

  • Make approve/reject decisions
  • Route based on amount thresholds
  • Branch based on risk levels (Low/Medium/High)
  • Direct to different approval levels
  • Any scenario where only ONE path should execute

Visual Example​

Result: Only ONE approval task executes (not both)


Implementation Options​

There are two ways to implement Exclusive Gateways:

The gateway itself contains a script that returns which flow to take.

BPMN XML Example​

<bpmn:exclusiveGateway id="Gateway_CheckAmount" 
name="Check Loan Amount"
default="Flow_Standard">
<bpmn:incoming>Flow_In</bpmn:incoming>
<bpmn:outgoing>Flow_HighValue</bpmn:outgoing>
<bpmn:outgoing>Flow_Standard</bpmn:outgoing>

<!-- Gateway-level condition -->
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
// Return the flow name or ID to take
if (loanAmount > 100000) {
return 'Flow_HighValue';
} else {
return 'Flow_Standard';
}
" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:exclusiveGateway>

<!-- Define the flows -->
<bpmn:sequenceFlow id="Flow_HighValue"
name="High Value (>$100k)"
sourceRef="Gateway_CheckAmount"
targetRef="Task_SeniorApproval" />

<bpmn:sequenceFlow id="Flow_Standard"
name="Standard Amount"
sourceRef="Gateway_CheckAmount"
targetRef="Task_StandardApproval" />

Fields Reference​

FieldLocationRequiredDescriptionExample
idGateway elementβœ… YesUnique gateway identifierGateway_CheckAmount
nameGateway elementβšͺ OptionalDescriptive name for the gateway"Check Loan Amount"
defaultGateway elementβœ… RecommendedFlow ID to take if condition fails/no matchFlow_Standard
ConditionExtension propertyβœ… Yes (Option 1)JavaScript that returns flow name/IDSee example
incomingGateway elementβœ… YesIncoming sequence flow IDFlow_In
outgoingGateway elementβœ… YesList of outgoing flow IDsMultiple <outgoing> tags

Script Context​

Your Condition script has access to:

  • All process variables: loanAmount, customerName, creditScore, etc.
  • Return value: Must be a string matching a flow id or name

Example Scripts:

// Simple threshold
return amount > 50000 ? 'Flow_Senior' : 'Flow_Standard';
// Multiple conditions
if (creditScore >= 720) {
return 'Flow_Approved';
} else if (creditScore >= 650) {
return 'Flow_Review';
} else {
return 'Flow_Rejected';
}
// Complex logic
const risk = calculateRiskScore(creditScore, debtToIncome, loanAmount);
if (risk < 30) return 'Flow_LowRisk';
if (risk < 60) return 'Flow_MediumRisk';
return 'Flow_HighRisk';

Execution Flow​

1. Process reaches gateway
2. Engine executes Condition script
3. Script returns flow name/ID (e.g., 'Flow_HighValue')
4. Engine finds matching flow by id or name
5. Process continues down that flow
6. Other flows are NOT executed

Execution Log Example​

[GATEWAY] Evaluating exclusive gateway: Gateway_CheckAmount (Check Loan Amount)
[GATEWAY] Available paths: 2
[GATEWAY] β†’ Flow_HighValue: 'High Value (>$100k)' β†’ Task_SeniorApproval
[GATEWAY] β†’ Flow_Standard: 'Standard Amount' β†’ Task_StandardApproval (DEFAULT)
[GATEWAY] Executing gateway-level condition script
[GATEWAY] Process variables: loanAmount=150000, creditScore=720
[GATEWAY] βœ“ Condition script returned: 'Flow_HighValue'
[GATEWAY] βœ“ DECISION: Taking flow 'Flow_HighValue' β†’ Task_SeniorApproval
[GATEWAY] Moving to element: Task_SeniorApproval

Option 2: Flow-Level Conditions​

Each outgoing flow has its own condition expression. The first flow whose condition evaluates to true is taken.

BPMN XML Example​

<bpmn:exclusiveGateway id="Gateway_RiskLevel" 
name="Risk Assessment"
default="Flow_Rejected">
<bpmn:incoming>Flow_In</bpmn:incoming>
<bpmn:outgoing>Flow_LowRisk</bpmn:outgoing>
<bpmn:outgoing>Flow_MediumRisk</bpmn:outgoing>
<bpmn:outgoing>Flow_HighRisk</bpmn:outgoing>
<bpmn:outgoing>Flow_Rejected</bpmn:outgoing>
</bpmn:exclusiveGateway>

<!-- Flow 1: Low Risk -->
<bpmn:sequenceFlow id="Flow_LowRisk"
name="Low Risk"
sourceRef="Gateway_RiskLevel"
targetRef="Task_AutoApprove">
<bpmn:conditionExpression>
creditScore >= 720 &amp;&amp; debtToIncomeRatio &lt; 36
</bpmn:conditionExpression>
</bpmn:sequenceFlow>

<!-- Flow 2: Medium Risk -->
<bpmn:sequenceFlow id="Flow_MediumRisk"
name="Medium Risk"
sourceRef="Gateway_RiskLevel"
targetRef="Task_OfficerReview">
<bpmn:conditionExpression>
creditScore >= 650 &amp;&amp; creditScore &lt; 720
</bpmn:conditionExpression>
</bpmn:sequenceFlow>

<!-- Flow 3: High Risk -->
<bpmn:sequenceFlow id="Flow_HighRisk"
name="High Risk"
sourceRef="Gateway_RiskLevel"
targetRef="Task_ManagerReview">
<bpmn:conditionExpression>
creditScore >= 580 &amp;&amp; creditScore &lt; 650
</bpmn:conditionExpression>
</bpmn:sequenceFlow>

<!-- Default Flow: No condition (fallback) -->
<bpmn:sequenceFlow id="Flow_Rejected"
name="Rejected"
sourceRef="Gateway_RiskLevel"
targetRef="Task_NotifyRejection" />

Fields Reference​

FieldLocationRequiredDescriptionExample
conditionExpressionInside <sequenceFlow>βšͺ OptionalBoolean JavaScript expressioncreditScore >= 720
defaultGateway elementβœ… RecommendedFlow ID if no conditions matchFlow_Rejected

Condition Expression Syntax​

Flow conditions must return true or false:

// Simple comparison
creditScore >= 720

// Multiple conditions (use XML entities)
creditScore >= 720 &amp;&amp; debtToIncomeRatio &lt; 36

// Complex expression
(amount > 50000 &amp;&amp; creditScore >= 700) || approvalOverride === true

XML Special Characters:

CharacterXML Entity
&&&amp;&amp;
<&lt;
>&gt;
"&quot;

Evaluation Order​

  1. Engine evaluates flows in the order they appear in XML (skips default flow)
  2. First condition that returns true wins
  3. If no conditions match, takes the default flow
  4. If no default and no matches β†’ process error

Execution Log Example​

[GATEWAY] Evaluating exclusive gateway: Gateway_RiskLevel (Risk Assessment)
[GATEWAY] Available paths: 4
[GATEWAY] β†’ Flow_LowRisk: 'Low Risk' β†’ Task_AutoApprove
[GATEWAY] β†’ Flow_MediumRisk: 'Medium Risk' β†’ Task_OfficerReview
[GATEWAY] β†’ Flow_HighRisk: 'High Risk' β†’ Task_ManagerReview
[GATEWAY] β†’ Flow_Rejected: 'Rejected' β†’ Task_NotifyRejection (DEFAULT)
[GATEWAY] Evaluating flow conditions...
[GATEWAY] Flow_LowRisk: false (creditScore=680, debtToIncomeRatio=42)
[GATEWAY] Flow_MediumRisk: true
[GATEWAY] βœ“ DECISION: Taking flow 'Flow_MediumRisk' β†’ Task_OfficerReview
[GATEWAY] Moving to element: Task_OfficerReview

Use Cases​

Use Case 1: Amount-Based Approval Routing​

Scenario: Route loan applications to different approval levels based on amount.

Requirements:

  • Loans < $50K β†’ Standard approval
  • Loans $50K-$250K β†’ Senior approval
  • Loans > $250K β†’ Executive approval

Implementation:

<bpmn:exclusiveGateway id="Gateway_Amount" default="Flow_Standard">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
if (loanAmount > 250000) return 'Flow_Executive';
if (loanAmount > 50000) return 'Flow_Senior';
return 'Flow_Standard';
" />
</custom:properties>
</bpmn:extensionElements>
<bpmn:outgoing>Flow_Executive</bpmn:outgoing>
<bpmn:outgoing>Flow_Senior</bpmn:outgoing>
<bpmn:outgoing>Flow_Standard</bpmn:outgoing>
</bpmn:exclusiveGateway>

Visual Flow:


Use Case 2: Risk-Based Routing​

Scenario: Different handling based on calculated risk score.

Requirements:

  • Low risk (score < 30) β†’ Auto-approve
  • Medium risk (30-60) β†’ Officer review
  • High risk (60-80) β†’ Manager review
  • Very high risk (>80) β†’ Auto-reject

Implementation:

<bpmn:exclusiveGateway id="Gateway_Risk" default="Flow_Reject">
<bpmn:outgoing>Flow_Auto</bpmn:outgoing>
<bpmn:outgoing>Flow_Officer</bpmn:outgoing>
<bpmn:outgoing>Flow_Manager</bpmn:outgoing>
<bpmn:outgoing>Flow_Reject</bpmn:outgoing>
</bpmn:exclusiveGateway>

<bpmn:sequenceFlow id="Flow_Auto" sourceRef="Gateway_Risk" targetRef="Task_Approve">
<bpmn:conditionExpression>riskScore &lt; 30</bpmn:conditionExpression>
</bpmn:sequenceFlow>

<bpmn:sequenceFlow id="Flow_Officer" sourceRef="Gateway_Risk" targetRef="Task_Officer">
<bpmn:conditionExpression>riskScore >= 30 &amp;&amp; riskScore &lt; 60</bpmn:conditionExpression>
</bpmn:sequenceFlow>

<bpmn:sequenceFlow id="Flow_Manager" sourceRef="Gateway_Risk" targetRef="Task_Manager">
<bpmn:conditionExpression>riskScore >= 60 &amp;&amp; riskScore &lt;= 80</bpmn:conditionExpression>
</bpmn:sequenceFlow>

Use Case 3: Status-Based Branching​

Scenario: Handle different customer account statuses differently.

<bpmn:exclusiveGateway id="Gateway_Status">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
switch(accountStatus) {
case 'ACTIVE': return 'Flow_Process';
case 'SUSPENDED': return 'Flow_Review';
case 'CLOSED': return 'Flow_Reject';
case 'PENDING': return 'Flow_Verify';
default: return 'Flow_Error';
}
" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:exclusiveGateway>

Execution Diagram​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 1. Process reaches Exclusive Gateway β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 2. Has gateway-level Condition? β”‚
β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ YES β”‚ NO
β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Execute script β”‚ β”‚ Evaluate each β”‚
β”‚ Get flow name/ID β”‚ β”‚ flow's condition β”‚
β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Find matching flow β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”
β”‚ Found? β”‚
β””β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”˜
YES β”‚ β”‚ NO
β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Take it β”‚ β”‚ Take defaultβ”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Continue on that path β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Best Practices​

βœ… DO:​

  1. Always set a default flow to handle unexpected cases
  2. Use descriptive flow names: "High Value (>$100k)" not "Flow_1"
  3. Keep conditions simple - move complex logic to ScriptTask before gateway
  4. Return exact flow IDs or names from gateway condition
  5. Test all branches to ensure each path is reachable

❌ DON'T:​

  1. Don't forget the default flow - process will error if no condition matches
  2. Don't use complex expressions directly in conditions - hard to debug
  3. Don't mix gateway-level and flow-level conditions - choose one approach
  4. Don't return flow names that don't exist - process will error
  5. Don't make multiple paths execute - use Inclusive or Parallel for that

Common Patterns​

Pattern: Pre-Calculate Decision Value​

Instead of complex gateway logic, calculate the decision value beforehand:

<!-- 1. Calculate decision value -->
<bpmn:scriptTask id="Task_Calculate" name="Calculate Route">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Script" value="
// Complex logic here
const riskScore = calculateRisk(creditScore, income, debt);
const employmentYears = calculateEmployment(startDate);

// Set simple decision variable
if (riskScore < 30 &amp;&amp; employmentYears > 2) {
approvalRoute = 'AUTO';
} else if (riskScore < 60) {
approvalRoute = 'OFFICER';
} else {
approvalRoute = 'MANAGER';
}
" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:scriptTask>

<!-- 2. Simple gateway uses calculated value -->
<bpmn:exclusiveGateway id="Gateway_Route">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
// Simple lookup
return 'Flow_' + approvalRoute;
" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:exclusiveGateway>

Benefits:

  • Gateway logic is simple and debuggable
  • Complex calculation logic is in one place
  • Easy to unit test the calculation separately

Troubleshooting​

Issue: Gateway takes default flow when it shouldn't​

Symptoms: Always goes to default path even when other conditions should match

Possible Causes:

  1. Condition script error
// ❌ Wrong - typo in variable name
return loanAmmount > 50000 ? 'Flow_High' : 'Flow_Low';

// βœ… Correct
return loanAmount > 50000 ? 'Flow_High' : 'Flow_Low';
  1. Flow name mismatch
// ❌ Wrong - returns name that doesn't exist
return 'HighValue'; // But flow is named 'Flow_HighValue'

// βœ… Correct - match exact flow id or name
return 'Flow_HighValue';
  1. Flow-level condition syntax error
<!-- ❌ Wrong - missing XML entity -->
<bpmn:conditionExpression>
amount > 50000 && creditScore >= 700
</bpmn:conditionExpression>

<!-- βœ… Correct - use XML entities -->
<bpmn:conditionExpression>
amount > 50000 &amp;&amp; creditScore >= 700
</bpmn:conditionExpression>

Check the logs:

[GATEWAY] βœ— WARNING: Condition returned 'xyz' but no matching flow found
[GATEWAY] Available flow IDs: Flow_HighValue, Flow_Standard
[GATEWAY] Available flow names: 'High Value', 'Standard'
[GATEWAY] ⚠ Falling back to default flow: Flow_Standard

Issue: Process throws error at gateway​

Symptoms: Process execution fails with error at gateway

Possible Causes:

  1. No default flow and no condition matches
<!-- ❌ Wrong - no default, condition can fail -->
<bpmn:exclusiveGateway id="Gateway_1">
<bpmn:outgoing>Flow_A</bpmn:outgoing>
<bpmn:outgoing>Flow_B</bpmn:outgoing>
</bpmn:exclusiveGateway>

<!-- βœ… Correct - always have default -->
<bpmn:exclusiveGateway id="Gateway_1" default="Flow_B">
  1. Gateway has no outgoing flows
<!-- ❌ Wrong - gateway leads nowhere -->
<bpmn:exclusiveGateway id="Gateway_1">
<bpmn:incoming>Flow_In</bpmn:incoming>
</bpmn:exclusiveGateway>

<!-- βœ… Correct - at least one outgoing -->
<bpmn:exclusiveGateway id="Gateway_1">
<bpmn:incoming>Flow_In</bpmn:incoming>
<bpmn:outgoing>Flow_Out</bpmn:outgoing>
</bpmn:exclusiveGateway>

Issue: Variable is undefined in condition​

Symptoms: Condition fails because variable doesn't exist

Solution: Set variables before gateway:

<!-- Prepare variables first -->
<bpmn:scriptTask id="Task_Prepare">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Script" value="
loanAmount = loanAmount || 0; // Default value
creditScore = creditScore || 0;
" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:scriptTask>

<bpmn:sequenceFlow sourceRef="Task_Prepare" targetRef="Gateway_Check" />

Summary​

Exclusive Gateway (XOR):

  • βœ… Chooses exactly ONE path from multiple options
  • βœ… Use for either/or decisions, routing, branching
  • βœ… Two implementation options: gateway-level condition OR flow-level conditions
  • βœ… Always set a default flow
  • βœ… Keep conditions simple and testable

Next Steps: