Gateway - Flow Control and Routing
Gateways control the flow of process execution by enabling conditional routing, parallel execution, and flow merging. They act as decision points in your process.
All three gateway types are fully implemented with token-based parallel execution support.
Gateway Types
1. Exclusive Gateway (XOR)
Behavior: Selects exactly ONE outgoing path based on conditions
Use When: You need to choose one path from multiple options
Implementation: ✅ Fully implemented with condition evaluation
2. Parallel Gateway (AND)
Behavior: Activates ALL outgoing paths simultaneously, waits for all incoming paths to complete
Use When: You need to execute multiple tasks in parallel
Implementation: ✅ Fully implemented with token-based parallel execution
3. Inclusive Gateway (OR)
Behavior: Activates one or MORE outgoing paths based on conditions
Use When: Multiple paths may be valid simultaneously
Implementation: ✅ Fully implemented with conditional token activation
Exclusive Gateway
The most commonly used gateway for decision-making.
Properties
Condition: Script that returns the flow name/ID to takeFormKey: Optional form for gateway configurationUserActions: Optional user actions at gatewayEntityState: State during gateway evaluationClientScript: Client-side script for UIResponsibleTeam/ResponsibleUser: Assignment for manual gateways
Basic Exclusive Gateway
<bpmn:exclusiveGateway id="Gateway_CheckAmount" name="High Value?" default="Flow_Standard">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
return loanAmount > 50000 ? 'Flow_HighValue' : 'Flow_Standard';
" />
</custom:properties>
</bpmn:extensionElements>
<bpmn:incoming>Flow_In</bpmn:incoming>
<bpmn:outgoing>Flow_HighValue</bpmn:outgoing>
<bpmn:outgoing>Flow_Standard</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:sequenceFlow id="Flow_HighValue" name="High Value"
sourceRef="Gateway_CheckAmount" targetRef="Task_SeniorReview" />
<bpmn:sequenceFlow id="Flow_Standard" name="Standard"
sourceRef="Gateway_CheckAmount" targetRef="Task_StandardReview" />
Multi-Way Decision Gateway
<bpmn:exclusiveGateway id="Gateway_RiskLevel" name="Assess Risk" default="Flow_Rejected">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
if (creditScore >= 720 && debtToIncomeRatio < 36) {
return 'Flow_LowRisk';
} else if (creditScore >= 650 && debtToIncomeRatio < 43) {
return 'Flow_MediumRisk';
} else if (creditScore >= 580) {
return 'Flow_HighRisk';
}
return 'Flow_Rejected'; // Default to rejection
" />
</custom:properties>
</bpmn:extensionElements>
<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>
<bpmn:sequenceFlow id="Flow_LowRisk" name="Low Risk (Auto-Approve)"
sourceRef="Gateway_RiskLevel" targetRef="Task_AutoApprove" />
<bpmn:sequenceFlow id="Flow_MediumRisk" name="Medium Risk (Officer Review)"
sourceRef="Gateway_RiskLevel" targetRef="Task_OfficerReview" />
<bpmn:sequenceFlow id="Flow_HighRisk" name="High Risk (Manager Review)"
sourceRef="Gateway_RiskLevel" targetRef="Task_ManagerReview" />
<bpmn:sequenceFlow id="Flow_Rejected" name="Rejected"
sourceRef="Gateway_RiskLevel" targetRef="Task_SendRejection" />
Gateway with BankLingo Commands
<bpmn:exclusiveGateway id="Gateway_DynamicRouting" name="Dynamic Approval Path">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
// Call BankLingo command to determine routing
const routingDecision = doCmd('DetermineApprovalPath', {
loanAmount: loanAmount,
creditScore: creditScore,
customerSegment: customerSegment,
riskFactors: riskFactors
});
// Return the flow name from command result
return routingDecision.flowName;
" />
</custom:properties>
</bpmn:extensionElements>
<bpmn:outgoing>Flow_AutoApprove</bpmn:outgoing>
<bpmn:outgoing>Flow_StandardReview</bpmn:outgoing>
<bpmn:outgoing>Flow_EnhancedReview</bpmn:outgoing>
<bpmn:outgoing>Flow_Escalate</bpmn:outgoing>
</bpmn:exclusiveGateway>
Gateway Based on User Action
<!-- User Task -->
<bpmn:userTask id="Task_Review" name="Review Application">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="FormKey" value="review-form" />
<custom:property name="UserActions" value="Approve,Reject,RequestMoreInfo" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:userTask>
<bpmn:sequenceFlow sourceRef="Task_Review" targetRef="Gateway_Decision" />
<!-- Gateway checks user action -->
<bpmn:exclusiveGateway id="Gateway_Decision" name="Check Decision" default="Flow_MoreInfo">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
if (userAction === 'Approve') return 'Flow_Approved';
if (userAction === 'Reject') return 'Flow_Rejected';
return 'Flow_MoreInfo';
" />
</custom:properties>
</bpmn:extensionElements>
<bpmn:outgoing>Flow_Approved</bpmn:outgoing>
<bpmn:outgoing>Flow_Rejected</bpmn:outgoing>
<bpmn:outgoing>Flow_MoreInfo</bpmn:outgoing>
</bpmn:exclusiveGateway>
Parallel Gateway
Execute multiple tasks simultaneously using true parallel execution.
The Parallel Gateway uses a token-based execution model where each path gets its own execution token, enabling genuinely concurrent processing.
How It Works
Split (Fork): When a Parallel Gateway has multiple outgoing flows:
- Creates an execution token for each outgoing path
- All tokens execute independently and simultaneously
- The parent token is deactivated (replaced by child tokens)
Join (Merge): When a Parallel Gateway has multiple incoming flows:
- Waits for ALL incoming tokens to arrive
- Once all paths complete, merges tokens into one
- Continues execution with a single token
Split - Start Parallel Execution
<bpmn:parallelGateway id="Gateway_Split" name="Parallel Processing">
<bpmn:incoming>Flow_In</bpmn:incoming>
<bpmn:outgoing>Flow_CreditCheck</bpmn:outgoing>
<bpmn:outgoing>Flow_IncomeVerification</bpmn:outgoing>
<bpmn:outgoing>Flow_DocumentReview</bpmn:outgoing>
</bpmn:parallelGateway>
<!-- All three tasks execute in parallel -->
<bpmn:sequenceFlow id="Flow_CreditCheck" sourceRef="Gateway_Split" targetRef="Task_CreditCheck" />
<bpmn:sequenceFlow id="Flow_IncomeVerification" sourceRef="Gateway_Split" targetRef="Task_VerifyIncome" />
<bpmn:sequenceFlow id="Flow_DocumentReview" sourceRef="Gateway_Split" targetRef="Task_ReviewDocs" />
<!-- Tasks run simultaneously with independent tokens -->
<bpmn:serviceTask id="Task_CreditCheck" name="Credit Bureau Check" />
<bpmn:serviceTask id="Task_VerifyIncome" name="Verify Income" />
<bpmn:userTask id="Task_ReviewDocs" name="Review Documents" />
Execution Logs You'll See:
"[PARALLEL GATEWAY] Type: SPLIT/FORK",
"[PARALLEL GATEWAY] Creating 3 parallel execution paths:",
"[PARALLEL GATEWAY] → Token 2a3f7e8b... → Credit Check → Task_CreditCheck",
"[PARALLEL GATEWAY] → Token 9c1d4a2f... → Income Verification → Task_VerifyIncome",
"[PARALLEL GATEWAY] → Token 7b6e3c9d... → Document Review → Task_ReviewDocs",
"[TOKEN PROCESSOR] Detected 3 active tokens. Processing parallel paths..."
Join - Wait for All Paths
<!-- Parallel tasks complete -->
<bpmn:sequenceFlow sourceRef="Task_CreditCheck" targetRef="Gateway_Join" />
<bpmn:sequenceFlow sourceRef="Task_VerifyIncome" targetRef="Gateway_Join" />
<bpmn:sequenceFlow sourceRef="Task_ReviewDocs" targetRef="Gateway_Join" />
<!-- Join gateway waits for ALL incoming flows -->
<bpmn:parallelGateway id="Gateway_Join" name="Wait for All">
<bpmn:incoming>Flow_FromCredit</bpmn:incoming>
<bpmn:incoming>Flow_FromIncome</bpmn:incoming>
<bpmn:incoming>Flow_FromDocs</bpmn:incoming>
<bpmn:outgoing>Flow_Continue</bpmn:outgoing>
</bpmn:parallelGateway>
<!-- Process continues only after all tasks complete -->
<bpmn:sequenceFlow id="Flow_Continue" sourceRef="Gateway_Join" targetRef="Task_MakeDecision" />
Execution Logs You'll See:
"[PARALLEL GATEWAY] Token 2a3f7e8b arrived at join. Total arrived: 1/3",
"[PARALLEL GATEWAY] ⏳ WAITING at join - 1/3 paths completed",
"[PARALLEL GATEWAY] Token 9c1d4a2f arrived at join. Total arrived: 2/3",
"[PARALLEL GATEWAY] ⏳ WAITING at join - 2/3 paths completed",
"[PARALLEL GATEWAY] Token 7b6e3c9d arrived at join. Total arrived: 3/3",
"[PARALLEL GATEWAY] ✓ All 3 paths completed. Merging and continuing...",
"[PARALLEL GATEWAY] Continuing to: Task_MakeDecision"
Complete Parallel Example
<bpmn:process id="ParallelApproval">
<bpmn:startEvent id="Start" />
<bpmn:sequenceFlow sourceRef="Start" targetRef="Gateway_ParallelSplit" />
<!-- Split: Start 3 parallel checks -->
<bpmn:parallelGateway id="Gateway_ParallelSplit" />
<!-- Path 1: Credit Check -->
<bpmn:sequenceFlow sourceRef="Gateway_ParallelSplit" targetRef="Task_CreditCheck" />
<bpmn:serviceTask id="Task_CreditCheck" name="Credit Bureau Check">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Endpoint" value="/api/credit-bureau/check" />
<custom:property name="Method" value="POST" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:serviceTask>
<bpmn:sequenceFlow sourceRef="Task_CreditCheck" targetRef="Gateway_ParallelJoin" />
<!-- Path 2: Income Verification -->
<bpmn:sequenceFlow sourceRef="Gateway_ParallelSplit" targetRef="Task_VerifyIncome" />
<bpmn:serviceTask id="Task_VerifyIncome" name="Verify Income">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Endpoint" value="/api/income/verify" />
<custom:property name="Method" value="POST" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:serviceTask>
<bpmn:sequenceFlow sourceRef="Task_VerifyIncome" targetRef="Gateway_ParallelJoin" />
<!-- Path 3: Fraud Check -->
<bpmn:sequenceFlow sourceRef="Gateway_ParallelSplit" targetRef="Task_FraudCheck" />
<bpmn:serviceTask id="Task_FraudCheck" name="Fraud Screening">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Endpoint" value="/api/fraud/screen" />
<custom:property name="Method" value="POST" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:serviceTask>
<bpmn:sequenceFlow sourceRef="Task_FraudCheck" targetRef="Gateway_ParallelJoin" />
<!-- Join: Wait for all 3 paths -->
<bpmn:parallelGateway id="Gateway_ParallelJoin" />
<bpmn:sequenceFlow sourceRef="Gateway_ParallelJoin" targetRef="Task_Consolidate" />
<!-- Continue after all complete -->
<bpmn:scriptTask id="Task_Consolidate" name="Consolidate Results">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Script" value="
// All three checks are complete - consolidate results
allChecksPassed =
creditCheckPassed &&
incomeVerified &&
!fraudDetected;
" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:scriptTask>
<bpmn:sequenceFlow sourceRef="Task_Consolidate" targetRef="End" />
<bpmn:endEvent id="End" />
</bpmn:process>
Inclusive Gateway
Multiple paths can be taken based on conditions (OR logic).
The Inclusive Gateway evaluates ALL outgoing flows and activates every path where the condition is true. It uses the same token-based execution model as Parallel Gateway.
Behavior
Split: Evaluates all flow conditions and creates tokens for flows that match:
- Evaluates gateway-level
Condition(if present) or individual flow conditions - Creates execution tokens for ALL flows where condition = true
- At least one flow must be activated
- If no conditions match, takes the default flow
Join: Same as Parallel Gateway - waits for all activated paths to complete
Basic Inclusive Gateway
<bpmn:inclusiveGateway id="Gateway_Notifications" name="Send Notifications" default="Flow_Email">
<bpmn:incoming>Flow_In</bpmn:incoming>
<bpmn:outgoing>Flow_Email</bpmn:outgoing>
<bpmn:outgoing>Flow_SMS</bpmn:outgoing>
<bpmn:outgoing>Flow_Push</bpmn:outgoing>
</bpmn:inclusiveGateway>
<!-- Email flow - always taken (default) -->
<bpmn:sequenceFlow id="Flow_Email" name="Email"
sourceRef="Gateway_Notifications" targetRef="Task_SendEmail" />
<!-- SMS flow - only if phone number exists -->
<bpmn:sequenceFlow id="Flow_SMS" name="SMS"
sourceRef="Gateway_Notifications" targetRef="Task_SendSMS">
<bpmn:conditionExpression>
customerPhone !== null && customerPhone !== ''
</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<!-- Push flow - only if mobile app installed -->
<bpmn:sequenceFlow id="Flow_Push" name="Push Notification"
sourceRef="Gateway_Notifications" targetRef="Task_SendPush">
<bpmn:conditionExpression>
hasMobileApp === true
</bpmn:conditionExpression>
</bpmn:sequenceFlow>
Execution Logs You'll See:
"[INCLUSIVE GATEWAY] Evaluating: Gateway_Notifications",
"[INCLUSIVE GATEWAY] Evaluating individual flow conditions:",
"[INCLUSIVE GATEWAY] Email: true",
"[INCLUSIVE GATEWAY] ✓ Activated: Email → Task_SendEmail",
"[INCLUSIVE GATEWAY] SMS: true",
"[INCLUSIVE GATEWAY] ✓ Activated: SMS → Task_SendSMS",
"[INCLUSIVE GATEWAY] Push Notification: false",
"[INCLUSIVE GATEWAY] ✓ Activated 2 paths. Creating tokens...",
"[INCLUSIVE GATEWAY] → Token 4f8a9b2c... → Email → Task_SendEmail",
"[INCLUSIVE GATEWAY] → Token 7d3e6a1f... → SMS → Task_SendSMS"
Gateway-Level Condition (Advanced)
You can use a gateway-level condition that returns multiple flow names:
<bpmn:inclusiveGateway id="Gateway_Alert" name="Alert Channels">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
// Determine which channels to use based on urgency
const channels = [];
if (urgency === 'high') {
channels.push('Flow_SMS', 'Flow_Email', 'Flow_Push');
} else if (urgency === 'medium') {
channels.push('Flow_Email', 'Flow_Push');
} else {
channels.push('Flow_Email');
}
// Return comma-separated flow names
return channels.join(',');
" />
</custom:properties>
</bpmn:extensionElements>
<bpmn:outgoing>Flow_Email</bpmn:outgoing>
<bpmn:outgoing>Flow_SMS</bpmn:outgoing>
<bpmn:outgoing>Flow_Push</bpmn:outgoing>
</bpmn:inclusiveGateway>
Inclusive Join Example
<!-- Multiple notification tasks complete -->
<bpmn:sequenceFlow sourceRef="Task_SendEmail" targetRef="Gateway_Join" />
<bpmn:sequenceFlow sourceRef="Task_SendSMS" targetRef="Gateway_Join" />
<bpmn:sequenceFlow sourceRef="Task_SendPush" targetRef="Gateway_Join" />
<!-- Join waits for all activated paths -->
<bpmn:inclusiveGateway id="Gateway_Join" name="Wait for Notifications">
<bpmn:incoming>Flow_AfterEmail</bpmn:incoming>
<bpmn:incoming>Flow_AfterSMS</bpmn:incoming>
<bpmn:incoming>Flow_AfterPush</bpmn:incoming>
<bpmn:outgoing>Flow_Continue</bpmn:outgoing>
</bpmn:inclusiveGateway>
<bpmn:sequenceFlow sourceRef="Gateway_Join" targetRef="Task_LogCompletion" />
Execution Logs You'll See:
"[INCLUSIVE GATEWAY] Token 4f8a9b2c arrived at join. Total: 1",
"[INCLUSIVE GATEWAY] ⏳ WAITING at join - 1/3 paths completed",
"[INCLUSIVE GATEWAY] Token 7d3e6a1f arrived at join. Total: 2",
"[INCLUSIVE GATEWAY] ✓ All activated paths completed. Merging..."
Gateway Evaluation Order
When a gateway is reached, the engine evaluates conditions in this order:
- Gateway
ConditionProperty: If present, execute the script and use returned flow name - Sequence Flow
conditionExpression: Evaluate each outgoing flow's condition - Default Flow: Take the flow marked as
default - First Flow: If nothing else matches, take the first outgoing flow
Example: Hybrid Approach
<bpmn:exclusiveGateway id="Gateway_Hybrid" default="Flow_Default">
<bpmn:extensionElements>
<custom:properties>
<!-- Try gateway condition first -->
<custom:property name="Condition" value="
// Complex logic using BankLingo commands
const decision = doCmd('GetApprovalDecision', {
customerId, loanAmount, creditScore
});
return decision.flowName; // Returns flow name or null
" />
</custom:properties>
</bpmn:extensionElements>
<bpmn:outgoing>Flow_AutoApprove</bpmn:outgoing>
<bpmn:outgoing>Flow_ManualReview</bpmn:outgoing>
<bpmn:outgoing>Flow_Default</bpmn:outgoing>
</bpmn:exclusiveGateway>
<!-- If gateway condition returns null or fails, evaluate flow conditions -->
<bpmn:sequenceFlow id="Flow_AutoApprove" sourceRef="Gateway_Hybrid" targetRef="Task_AutoApprove">
<bpmn:conditionExpression>creditScore >= 720 && loanAmount < 25000</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<bpmn:sequenceFlow id="Flow_ManualReview" sourceRef="Gateway_Hybrid" targetRef="Task_ManualReview">
<bpmn:conditionExpression>creditScore >= 580</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<!-- If nothing matches, take default -->
<bpmn:sequenceFlow id="Flow_Default" sourceRef="Gateway_Hybrid" targetRef="Task_Reject" />
Use Cases
1. Amount-Based Routing
<bpmn:exclusiveGateway id="Gateway_Amount">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
if (amount < 10000) return 'Flow_Tier1';
if (amount < 50000) return 'Flow_Tier2';
if (amount < 100000) return 'Flow_Tier3';
return 'Flow_BoardApproval';
" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:exclusiveGateway>
2. Customer Segment Routing
<bpmn:exclusiveGateway id="Gateway_Segment">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
if (customerSegment === 'VIP') return 'Flow_VIPProcess';
if (customerSegment === 'Premium') return 'Flow_PremiumProcess';
return 'Flow_StandardProcess';
" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:exclusiveGateway>
3. Time-Based Routing
<bpmn:exclusiveGateway id="Gateway_Time">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
const hour = new Date().getHours();
const dayOfWeek = new Date().getDay();
// Weekend or after hours
if (dayOfWeek === 0 || dayOfWeek === 6 || hour < 8 || hour >= 17) {
return 'Flow_AfterHours';
}
return 'Flow_BusinessHours';
" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:exclusiveGateway>
4. Complex Multi-Factor Decision
<bpmn:exclusiveGateway id="Gateway_ComplexDecision">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
// Score-based routing with multiple factors
let routingScore = 0;
if (creditScore >= 720) routingScore += 40;
else if (creditScore >= 650) routingScore += 20;
else if (creditScore < 580) return 'Flow_Reject';
if (debtToIncomeRatio < 36) routingScore += 30;
else if (debtToIncomeRatio > 43) return 'Flow_Reject';
if (customerTenureYears > 5) routingScore += 15;
if (numberOfAccounts > 3) routingScore += 10;
if (loanAmount > 100000) routingScore -= 20;
// Route based on total score
if (routingScore >= 70) return 'Flow_AutoApprove';
if (routingScore >= 50) return 'Flow_StandardReview';
if (routingScore >= 30) return 'Flow_EnhancedReview';
return 'Flow_SeniorReview';
" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:exclusiveGateway>
Best Practices
1. Always Set a Default Flow
<bpmn:exclusiveGateway id="Gateway_1" default="Flow_Default">
This ensures the process never gets stuck if no conditions match.
2. Name Flows Clearly
<bpmn:sequenceFlow id="Flow_HighValue" name="Amount > $50,000" ... />
Use descriptive names that explain the routing logic.
3. Keep Conditions Simple
Move complex logic to script tasks before the gateway:
<!-- Good: Calculate first, then route -->
<bpmn:scriptTask id="Task_Calculate" name="Calculate Routing">
<custom:property name="Script" value="routingDecision = complexCalculation();" />
</bpmn:scriptTask>
<bpmn:exclusiveGateway id="Gateway_Route">
<custom:property name="Condition" value="return routingDecision;" />
</bpmn:exclusiveGateway>
4. Document Gateway Logic
<bpmn:exclusiveGateway id="Gateway_Risk" name="Risk Assessment">
<bpmn:documentation>
Routes applications based on risk score:
- Low Risk (Score >= 70): Auto-approve
- Medium Risk (Score 50-69): Officer review
- High Risk (Score < 50): Manager review
</bpmn:documentation>
</bpmn:exclusiveGateway>
5. Use Meaningful Variable Names
// Good
return creditScore >= 720 ? 'Flow_Approved' : 'Flow_Review';
// Avoid
return cs >= 720 ? 'f1' : 'f2';
Debugging Gateways
Enable execution logging to see gateway decisions:
{
"executionLogs": [
"[GATEWAY] Evaluating exclusive gateway: Gateway_RiskLevel",
"[GATEWAY] Executing gateway condition script",
"[GATEWAY] Condition returned: Flow_MediumRisk",
"[GATEWAY] Taking flow: Flow_MediumRisk → Task_OfficerReview"
]
}
Next Steps
- UserTask - Combine with user decisions
- ScriptTask - Calculate routing logic
- Examples - Complete routing examples
- Execution Modes - Debug gateway decisions