Inclusive Gateway (OR)
The Inclusive Gateway (also known as OR Gateway) is a hybrid gateway that activates one or more outgoing paths based on conditions. It combines features of both Exclusive and Parallel Gateways.
Symbol
In BPMN diagrams, it's shown as a diamond with an "O" (circle) marker.
When to Use
Use Inclusive Gateways when you need to:
- Send notifications through multiple channels (some optional based on availability)
- Perform optional checks based on data presence
- Route to multiple teams conditionally
- Execute a subset of parallel tasks based on conditions
- Any scenario where 1 or more paths should execute (not all, not just one)
Comparison with Other Gateways
| Gateway | Paths Activated | Based On |
|---|---|---|
| Exclusive (XOR) | Exactly 1 | Condition selects which one |
| Parallel (AND) | All (always) | No conditions (unconditional) |
| Inclusive (OR) | 1 or more | Each path has own condition |
Visual Example
Result: Email always + SMS if customer has phone + Push if customer has app
Token-Based Execution Model
Like Parallel Gateways, Inclusive Gateways use token-based execution:
How It Works
Key Difference from Parallel Gateway
- Parallel Gateway: ALL paths activated (no conditions)
- Inclusive Gateway: Paths activated ONLY if their condition =
true
The join gateway knows which paths were activated and waits only for those specific tokens.
Implementation Options
Option 1: Flow-Level Conditions (Recommended)
Each outgoing flow has its own condition. All flows where condition evaluates to true are activated.
BPMN XML Example
<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>
<!-- Flow 1: Email (always sent - no condition means always active) -->
<bpmn:sequenceFlow id="Flow_Email"
name="Email Notification"
sourceRef="Gateway_Notifications"
targetRef="Task_SendEmail" />
<!-- Flow 2: SMS (only if phone exists) -->
<bpmn:sequenceFlow id="Flow_SMS"
name="SMS Notification"
sourceRef="Gateway_Notifications"
targetRef="Task_SendSMS">
<bpmn:conditionExpression>
customerPhone !== null && customerPhone !== ''
</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<!-- Flow 3: Push (only if app installed) -->
<bpmn:sequenceFlow id="Flow_Push"
name="Push Notification"
sourceRef="Gateway_Notifications"
targetRef="Task_SendPush">
<bpmn:conditionExpression>
hasMobileApp === true
</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<!-- Tasks execute if their flow was activated -->
<bpmn:serviceTask id="Task_SendEmail" name="Send Email" />
<bpmn:serviceTask id="Task_SendSMS" name="Send SMS" />
<bpmn:serviceTask id="Task_SendPush" name="Send Push Notification" />
<!-- Connect all to join gateway -->
<bpmn:sequenceFlow sourceRef="Task_SendEmail" targetRef="Gateway_NotificationJoin" />
<bpmn:sequenceFlow sourceRef="Task_SendSMS" targetRef="Gateway_NotificationJoin" />
<bpmn:sequenceFlow sourceRef="Task_SendPush" targetRef="Gateway_NotificationJoin" />
<!-- JOIN: Wait for all activated paths to complete -->
<bpmn:inclusiveGateway id="Gateway_NotificationJoin"
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 id="Flow_Continue"
sourceRef="Gateway_NotificationJoin"
targetRef="Task_Next" />
Fields Reference
| Field | Location | Required | Description | Example |
|---|---|---|---|---|
id | Gateway element | ✅ Yes | Unique gateway identifier | Gateway_Notifications |
name | Gateway element | ⚪ Optional | Descriptive name | "Send Notifications" |
default | Gateway element | ✅ Recommended | Flow activated if no conditions match | Flow_Email |
conditionExpression | Inside <sequenceFlow> | ⚪ Optional | Boolean expression | See examples |
Condition Rules
- Flows WITHOUT conditions → Always activated
- Flows WITH conditions → Activated only if condition evaluates to
true - At least ONE flow must activate → Use
defaultto guarantee this
Possible Outcomes
Based on the example above:
| Customer Data | Flows Activated | Tokens Created | Tasks Executed |
|---|---|---|---|
| Email only | Flow_Email | 1 | Email sent |
| Email + Phone | Flow_Email, Flow_SMS | 2 | Email + SMS sent |
| Email + Phone + App | Flow_Email, Flow_SMS, Flow_Push | 3 | All three sent |
| No phone, has app | Flow_Email, Flow_Push | 2 | Email + Push sent |
Option 2: Gateway-Level Condition (Advanced)
The gateway itself contains a script that returns a comma-separated list of flow names/IDs to activate.
BPMN XML Example
<bpmn:inclusiveGateway id="Gateway_Alerts"
name="Alert Channels">
<bpmn:incoming>Flow_In</bpmn:incoming>
<bpmn:outgoing>Flow_Email</bpmn:outgoing>
<bpmn:outgoing>Flow_SMS</bpmn:outgoing>
<bpmn:outgoing>Flow_Push</bpmn:outgoing>
<!-- Gateway-level condition -->
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
// Build list of channels based on urgency
const channels = [];
// Email always included
channels.push('Flow_Email');
// SMS for high/medium urgency
if (urgency === 'high' || urgency === 'medium') {
channels.push('Flow_SMS');
}
// Push for high urgency only
if (urgency === 'high') {
channels.push('Flow_Push');
}
// Return comma-separated flow names or IDs
return channels.join(',');
" />
</custom:properties>
</bpmn:extensionElements>
</bpmn:inclusiveGateway>
Return Value Format
The script must return a comma-separated string of flow IDs or names:
// By flow ID
return 'Flow_Email,Flow_SMS';
// By flow name
return 'Email Notification,SMS Notification';
// Single flow
return 'Flow_Email';
// All flows
return 'Flow_Email,Flow_SMS,Flow_Push';
When to Use Gateway-Level vs Flow-Level
| Use Gateway-Level Condition When | Use Flow-Level Conditions When |
|---|---|
| Complex logic determines which paths | Each path has simple independent condition |
| Multiple factors affect decision | Conditions don't overlap |
| Need centralized decision logic | Prefer declarative approach |
| Conditions share common calculations | Each condition is self-contained |
Execution Flow
Split Execution
┌─────────────────────────────────────┐
│ Token arrives at Inclusive Gateway │
└───────────────┬─────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Evaluate conditions for each flow │
│ (or execute gateway condition) │
└───────────────┬─────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Identify flows where condition=true │
│ (or parse gateway condition result) │
└───────────────┬─────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Create token for EACH active flow │
└───────────────┬─────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Record which tokens were created │
│ (join will wait for these) │
└───────────────┬─────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Deactivate parent token │
└───────────────┬─────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Activated tokens execute │
└─────────────────────────────────────┘
Join Execution
┌─────────────────────────────────────┐
│ Token arrives at join gateway │
└───────────────┬─────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Record token arrival │
└───────────────┬─────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Check: Were all activated tokens │
│ from split received? │
└───────┬───────────────┬─────────────┘
│ NO │ YES
▼ ▼
┌──────────────┐ ┌────────────────────┐
│ Deactivate │ │ All activated paths│
│ this token │ │ completed │
│ │ └─────────┬──────────┘
│ WAIT for │ │
│ remaining │ ▼
└──────────────┘ ┌────────────────────┐
│ Merge tokens │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ Continue process │
└────────────────────┘
Execution Log Examples
Example 1: Two Paths Activated
Scenario: Customer has email and phone, but no app.
[INCLUSIVE GATEWAY] Evaluating inclusive gateway: Gateway_Notifications (Send Notifications)
[INCLUSIVE GATEWAY] Incoming flows: 1
[INCLUSIVE GATEWAY] Outgoing flows: 3
[INCLUSIVE GATEWAY] Evaluating individual flow conditions:
[INCLUSIVE GATEWAY] Flow: Flow_Email (Email Notification)
[INCLUSIVE GATEWAY] No condition → Always active
[INCLUSIVE GATEWAY] ✓ Activated
[INCLUSIVE GATEWAY] Flow: Flow_SMS (SMS Notification)
[INCLUSIVE GATEWAY] Condition: customerPhone !== null && customerPhone !== ''
[INCLUSIVE GATEWAY] Variables: customerPhone="+1234567890"
[INCLUSIVE GATEWAY] Evaluation result: true
[INCLUSIVE GATEWAY] ✓ Activated
[INCLUSIVE GATEWAY] Flow: Flow_Push (Push Notification)
[INCLUSIVE GATEWAY] Condition: hasMobileApp === true
[INCLUSIVE GATEWAY] Variables: hasMobileApp=false
[INCLUSIVE GATEWAY] Evaluation result: false
[INCLUSIVE GATEWAY] ✗ Not activated
[INCLUSIVE GATEWAY] ✓ Activated 2 paths out of 3
[INCLUSIVE GATEWAY] Creating execution tokens:
[INCLUSIVE GATEWAY] → Token 4f8a9b2c-3d5e-4f7a-8c6b-5d7e9f1a2b3c
[INCLUSIVE GATEWAY] Path: Email Notification → Task_SendEmail
[INCLUSIVE GATEWAY] → Token 7d3e6a1f-2c4b-4e9a-7f5d-8c9a1b2e3f4a
[INCLUSIVE GATEWAY] Path: SMS Notification → Task_SendSMS
[INCLUSIVE GATEWAY] Recording activated token count: 2
[INCLUSIVE GATEWAY] ✓ Inclusive split complete. 2 tokens now active.
[TOKEN PROCESSOR] Detected 2 active tokens. Processing conditional parallel paths...
[TOKEN 4f8a9b2c] Processing Task_SendEmail...
[TOKEN 4f8a9b2c] ✓ Email sent successfully
[TOKEN 7d3e6a1f] Processing Task_SendSMS...
[TOKEN 7d3e6a1f] ✓ SMS sent successfully
[INCLUSIVE GATEWAY] Token 4f8a9b2c arrived at join: Gateway_NotificationJoin
[INCLUSIVE GATEWAY] Expected tokens from split: 2
[INCLUSIVE GATEWAY] Arrived so far: 1
[INCLUSIVE GATEWAY] ⏳ WAITING - 1/2 activated paths completed
[INCLUSIVE GATEWAY] Token 7d3e6a1f arrived at join: Gateway_NotificationJoin
[INCLUSIVE GATEWAY] Expected tokens from split: 2
[INCLUSIVE GATEWAY] Arrived so far: 2
[INCLUSIVE GATEWAY] ✓ ALL ACTIVATED PATHS COMPLETED (2/2)
[INCLUSIVE GATEWAY] Merging tokens and continuing...
[INCLUSIVE GATEWAY] Created merged token: 9e2f5a8b-7c4d-4f6a-9b3e-2d5c7a1f4b8e
[INCLUSIVE GATEWAY] Moving to: Task_Next
Example 2: All Paths Activated
Scenario: Customer has email, phone, and app.
[INCLUSIVE GATEWAY] Evaluating inclusive gateway: Gateway_Notifications
[INCLUSIVE GATEWAY] ✓ Activated 3 paths out of 3
[INCLUSIVE GATEWAY] Creating execution tokens:
[INCLUSIVE GATEWAY] → Token abc... (Email)
[INCLUSIVE GATEWAY] → Token def... (SMS)
[INCLUSIVE GATEWAY] → Token ghi... (Push)
[INCLUSIVE GATEWAY] Recording activated token count: 3
... tasks execute ...
[INCLUSIVE GATEWAY] ✓ ALL ACTIVATED PATHS COMPLETED (3/3)
[INCLUSIVE GATEWAY] Merging 3 tokens and continuing...
Example 3: Gateway-Level Condition
[INCLUSIVE GATEWAY] Evaluating inclusive gateway: Gateway_Alerts (Alert Channels)
[INCLUSIVE GATEWAY] Gateway has Condition property
[INCLUSIVE GATEWAY] Executing gateway-level condition script
[INCLUSIVE GATEWAY] Variables: urgency='high'
[INCLUSIVE GATEWAY] ✓ Condition returned: 'Flow_Email,Flow_SMS,Flow_Push'
[INCLUSIVE GATEWAY] Parsing activated flow list...
[INCLUSIVE GATEWAY] ✓ Matched: Flow_Email → Task_Email
[INCLUSIVE GATEWAY] ✓ Matched: Flow_SMS → Task_SMS
[INCLUSIVE GATEWAY] ✓ Matched: Flow_Push → Task_Push
[INCLUSIVE GATEWAY] ✓ Activated 3 paths. Creating tokens...
Use Cases
Use Case 1: Multi-Channel Customer Notifications
Scenario: Notify customer through all available channels.
Requirements:
- Email is mandatory (always send)
- SMS if customer has phone number
- Push notification if customer has mobile app
- In-app notification if customer is logged in
Implementation:
<bpmn:inclusiveGateway id="Gateway_CustomerNotify" default="Flow_Email">
<bpmn:outgoing>Flow_Email</bpmn:outgoing>
<bpmn:outgoing>Flow_SMS</bpmn:outgoing>
<bpmn:outgoing>Flow_Push</bpmn:outgoing>
<bpmn:outgoing>Flow_InApp</bpmn:outgoing>
</bpmn:inclusiveGateway>
<!-- Email: no condition = always -->
<bpmn:sequenceFlow id="Flow_Email" sourceRef="Gateway_CustomerNotify" targetRef="Task_Email" />
<!-- SMS: conditional -->
<bpmn:sequenceFlow id="Flow_SMS" sourceRef="Gateway_CustomerNotify" targetRef="Task_SMS">
<bpmn:conditionExpression>
phoneNumber !== null && phoneNumber !== ''
</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<!-- Push: conditional -->
<bpmn:sequenceFlow id="Flow_Push" sourceRef="Gateway_CustomerNotify" targetRef="Task_Push">
<bpmn:conditionExpression>
hasMobileApp === true && pushToken !== null
</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<!-- In-App: conditional -->
<bpmn:sequenceFlow id="Flow_InApp" sourceRef="Gateway_CustomerNotify" targetRef="Task_InApp">
<bpmn:conditionExpression>
isLoggedIn === true && sessionId !== null
</bpmn:conditionExpression>
</bpmn:sequenceFlow>
Visual Flow:
Notification
◇O
╱ | | ╲
Email SMS Push InApp
✓ ? ? ?
╲ | | ╱
◇O
│
Continue
Use Case 2: Conditional Verification Checks
Scenario: Perform different verifications based on data availability.
Requirements:
- Credit check always required
- Employment verification if applicant is employed
- Business verification if self-employed
- Reference check if references provided
Implementation:
<bpmn:inclusiveGateway id="Gateway_Verifications" default="Flow_Credit">
<bpmn:outgoing>Flow_Credit</bpmn:outgoing>
<bpmn:outgoing>Flow_Employment</bpmn:outgoing>
<bpmn:outgoing>Flow_Business</bpmn:outgoing>
<bpmn:outgoing>Flow_References</bpmn:outgoing>
</bpmn:inclusiveGateway>
<!-- Credit: always (no condition) -->
<bpmn:sequenceFlow id="Flow_Credit" sourceRef="Gateway_Verifications"
targetRef="Task_CreditCheck" />
<!-- Employment: if employed -->
<bpmn:sequenceFlow id="Flow_Employment" sourceRef="Gateway_Verifications"
targetRef="Task_EmploymentVerify">
<bpmn:conditionExpression>
employmentStatus === 'EMPLOYED' && employerName !== null
</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<!-- Business: if self-employed -->
<bpmn:sequenceFlow id="Flow_Business" sourceRef="Gateway_Verifications"
targetRef="Task_BusinessVerify">
<bpmn:conditionExpression>
employmentStatus === 'SELF_EMPLOYED' && businessName !== null
</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<!-- References: if provided -->
<bpmn:sequenceFlow id="Flow_References" sourceRef="Gateway_Verifications"
targetRef="Task_CheckReferences">
<bpmn:conditionExpression>
references !== null && references.length > 0
</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<!-- Join after all activated checks complete -->
<bpmn:inclusiveGateway id="Gateway_VerificationsJoin">
<bpmn:incoming>Flow_FromCredit</bpmn:incoming>
<bpmn:incoming>Flow_FromEmployment</bpmn:incoming>
<bpmn:incoming>Flow_FromBusiness</bpmn:incoming>
<bpmn:incoming>Flow_FromReferences</bpmn:incoming>
<bpmn:outgoing>Flow_ToDecision</bpmn:outgoing>
</bpmn:inclusiveGateway>
Possible Outcomes:
| Applicant Type | Checks Activated | Paths |
|---|---|---|
| Employed, no refs | Credit + Employment | 2 |
| Self-employed, has refs | Credit + Business + References | 3 |
| Unemployed, no refs | Credit only | 1 |
| Employed, has refs | Credit + Employment + References | 3 |
Use Case 3: Alert Escalation Based on Severity
Scenario: Send alerts to different teams based on issue severity.
<bpmn:inclusiveGateway id="Gateway_Alerts">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="Condition" value="
const teams = [];
// Support always notified
teams.push('Flow_Support');
// Manager for medium/high severity
if (severity === 'MEDIUM' || severity === 'HIGH') {
teams.push('Flow_Manager');
}
// Executive for high severity
if (severity === 'HIGH') {
teams.push('Flow_Executive');
}
// Security team for security issues
if (category === 'SECURITY') {
teams.push('Flow_Security');
}
return teams.join(',');
" />
</custom:properties>
</bpmn:extensionElements>
<bpmn:outgoing>Flow_Support</bpmn:outgoing>
<bpmn:outgoing>Flow_Manager</bpmn:outgoing>
<bpmn:outgoing>Flow_Executive</bpmn:outgoing>
<bpmn:outgoing>Flow_Security</bpmn:outgoing>
</bpmn:inclusiveGateway>
Examples:
| Issue | Teams Notified |
|---|---|
| Low severity, non-security | Support only |
| Medium severity, non-security | Support + Manager |
| High severity, non-security | Support + Manager + Executive |
| Low severity, security | Support + Security |
| High severity, security | Support + Manager + Executive + Security |
Best Practices
✅ DO
- Set a default flow to ensure at least one path activates
- Use flow-level conditions for simple cases - easier to understand
- Use gateway-level condition for complex logic - centralized decision
- Test all combinations - ensure each subset of paths works
- Match split and join - join must have all possible incoming flows
❌ DON'T
- Don't make all conditions mutually exclusive - use Exclusive Gateway instead
- Don't forget the default flow - at least one path must activate
- Don't modify shared variables in parallel paths - can cause race conditions
- Don't forget the join - activated paths must synchronize
- Don't confuse with Parallel Gateway - Inclusive is conditional, Parallel is not
Common Patterns
Pattern 1: Mandatory + Optional Paths
◇O
╱ | ╲
M O O M=Mandatory, O=Optional
╲ | ╱
◇O
One path always activates, others conditional
Pattern 2: Multiple Optional Paths
◇O
╱ | ╲
O O O All optional
╲ | ╱
◇O
At least one must activate (use default)
Pattern 3: Nested Inclusive
◇O Main
╱ ╲
A ◇O Nested
╱ ╲
B C
Nested gateway operates on its own token
Troubleshooting
Issue: No paths activate
Symptoms: Process error - "No flows activated at inclusive gateway"
Cause: All conditions false and no default flow
Solution:
<!-- ❌ Wrong - no default, all conditions can be false -->
<bpmn:inclusiveGateway id="Gateway_1">
<bpmn:outgoing>Flow_A</bpmn:outgoing>
<bpmn:outgoing>Flow_B</bpmn:outgoing>
</bpmn:inclusiveGateway>
<bpmn:sequenceFlow id="Flow_A">
<bpmn:conditionExpression>hasPhoneNum === true</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<bpmn:sequenceFlow id="Flow_B">
<bpmn:conditionExpression>hasEmail === true</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<!-- ✅ Correct - add default -->
<bpmn:inclusiveGateway id="Gateway_1" default="Flow_A">
Issue: Join waits indefinitely
Symptoms: Log shows ⏳ WAITING - 2/3 activated paths completed forever
Cause: Mismatch between activated paths and join expectations
Check:
[INCLUSIVE GATEWAY] Activated 2 paths ← Split created 2 tokens
[INCLUSIVE GATEWAY] Expected: 3 ← Join expects 3
Solution: This indicates a bug. Join should only wait for activated tokens. Check logs to see which tokens were created at split.
Issue: Activates wrong paths
Symptoms: Wrong tasks execute
Cause: Condition logic error
Debug Steps:
- Check execution logs for condition evaluation:
[INCLUSIVE GATEWAY] Flow: Flow_SMS
[INCLUSIVE GATEWAY] Condition: customerPhone !== null
[INCLUSIVE GATEWAY] Variables: customerPhone=null ← Check this
[INCLUSIVE GATEWAY] Evaluation result: false
- Verify variable values are set before gateway
- Check condition syntax (XML entities for
&&,<,>)
Issue: Gateway-level condition returns invalid flow
Symptoms: Warning in logs, falls back to default
[INCLUSIVE GATEWAY] ✗ WARNING: Condition returned 'Flow_XYZ' but no such flow exists
[INCLUSIVE GATEWAY] Available flows: Flow_Email, Flow_SMS, Flow_Push
[INCLUSIVE GATEWAY] ⚠ Falling back to default flow
Solution: Ensure script returns exact flow IDs or names:
// ❌ Wrong - typo in flow name
return 'Flow_Email,Flow_Sms'; // Should be 'Flow_SMS'
// ✅ Correct
return 'Flow_Email,Flow_SMS';
Summary
Inclusive Gateway (OR):
- ✅ Activates one or more paths based on conditions
- ✅ Creates token for each path where condition =
true - ✅ Join waits for all activated tokens (not all possible tokens)
- ✅ Use for conditional parallelism
- ✅ Two options: flow-level or gateway-level conditions
- ✅ Always set a
defaultflow to ensure at least one activates
When to Use:
- Multiple optional parallel tasks
- Notification to available channels
- Conditional data collection
- Alert escalation based on rules
Next Steps:
- See Parallel Gateway for unconditional parallel execution
- See Exclusive Gateway for single-path decisions
- Browse Use Cases for more examples
- Check Troubleshooting for common issues