Call Activity - Subprocess Execution
Call Activity invokes another process as a reusable subprocess. It enables process composition, orchestration, and reusability across your workflow ecosystem.
Overview
A Call Activity (also known as Subprocess) allows you to:
- ✅ Reuse processes across multiple workflows
- ✅ Isolate complexity in separate process definitions
- ✅ Execute in parallel with multi-instance (Phase 1)
- ✅ Wait for completion with automatic callbacks (Phase 3)
- ✅ Pass data between parent and child processes
Properties
Required Properties
| Property | Type | Required | Description |
|---|---|---|---|
calledElement | string | Yes | Process definition ID to call |
TaskType | string | Yes | Must be "CallActivity" |
Name | string | Yes | Display name |
Optional Properties
| Property | Type | Default | Description |
|---|---|---|---|
inputMapping | JSON | null | Map parent variables to subprocess |
outputMapping | JSON | null | Map subprocess results to parent |
resultVariable | string | lastSubprocessResult | Variable to store subprocess result |
asyncBefore | boolean | false | Execute asynchronously (Phase 2) |
isMultiInstance | boolean | false | Execute multiple times (Phase 1) |
collection | expression | null | Array for multi-instance execution |
elementVariable | string | item | Variable name for current item |
Basic Call Activity
Simple Subprocess Call
<bpmn:callActivity id="ValidateCustomer"
name="Validate Customer"
calledElement="CustomerValidationProcess">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="inputMapping" value="{
"customerId": "context.customerId",
"validationType": "FULL"
}"/>
<custom:property name="outputMapping" value="{
"validationResult": "subprocess.result",
"validationErrors": "subprocess.errors"
}"/>
<custom:property name="resultVariable" value="customerValidation"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:callActivity>
How it works:
- Parent process reaches Call Activity
- New subprocess instance created with
CustomerValidationProcessdefinition - Input mapping transfers data from parent to subprocess
- Parent process waits for subprocess completion (callback mechanism)
- When subprocess completes, output mapping transfers results back
- Parent process continues
Input/Output Mapping
Input Mapping (Parent → Subprocess)
Transfer data from parent process to subprocess:
<custom:property name="inputMapping" value="{
"subprocessVariable": "context.parentVariable",
"applicantId": "context.customerId",
"requestDate": "context.applicationDate",
"amount": "context.loanAmount"
}"/>
Syntax:
- Key: Variable name in subprocess
- Value: Expression to evaluate in parent context (usually
context.variableName)
Output Mapping (Subprocess → Parent)
Transfer results from subprocess back to parent:
<custom:property name="outputMapping" value="{
"parentVariable": "subprocess.subprocessVariable",
"approvalStatus": "subprocess.status",
"approvalComments": "subprocess.comments",
"approvedBy": "subprocess.approverId"
}"/>
Syntax:
- Key: Variable name in parent context
- Value: Expression to read from subprocess result (prefix with
subprocess.)
Result Variable
Store the entire subprocess result:
<custom:property name="resultVariable" value="validationResult"/>
Access in parent process:
// Access full subprocess result
var result = context.validationResult;
logger.info('Subprocess status: ' + result.status);
logger.info('Subprocess variables: ' + JSON.stringify(result.variables));
Multi-Instance Subprocess (Phase 1)
Execute a subprocess multiple times in parallel or sequence.
Parallel Execution
Process multiple items simultaneously:
<bpmn:callActivity id="ProcessOrders"
name="Process Each Order"
calledElement="OrderProcessingWorkflow">
<bpmn:multiInstanceLoopCharacteristics isSequential="false">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="collection" value="context.orders"/>
<custom:property name="elementVariable" value="currentOrder"/>
<custom:property name="aggregationVariable" value="orderResults"/>
<custom:property name="completionCondition" value="context.processedCount >= 10"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:multiInstanceLoopCharacteristics>
<bpmn:extensionElements>
<custom:properties>
<custom:property name="inputMapping" value="{
"order": "currentOrder",
"processingMode": "BATCH"
}"/>
<custom:property name="outputMapping" value="{
"processedOrder": "subprocess.result"
}"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:callActivity>
Multi-Instance Properties:
| Property | Type | Required | Description |
|---|---|---|---|
collection | expression | Yes | Array to iterate (e.g., context.orders) |
elementVariable | string | No | Variable name for current item (default: item) |
aggregationVariable | string | No | Collect all results (default: results) |
completionCondition | expression | No | Early termination condition |
isSequential | boolean | No | Sequential vs parallel (default: false = parallel) |
Execution Flow:
Parent Process
↓
Call Activity (Multi-Instance)
↓
├─→ Subprocess Instance 1 (Order 1) ──→ Result 1
├─→ Subprocess Instance 2 (Order 2) ──→ Result 2
├─→ Subprocess Instance 3 (Order 3) ──→ Result 3
└─→ Subprocess Instance N (Order N) ──→ Result N
↓
Aggregate Results
↓
Continue Parent Process
Sequential Execution
Process items one at a time:
<bpmn:callActivity id="ApprovalChain"
name="Manager Approval Chain"
calledElement="ManagerApprovalProcess">
<bpmn:multiInstanceLoopCharacteristics isSequential="true">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="collection" value="context.approvalManagers"/>
<custom:property name="elementVariable" value="currentManager"/>
<custom:property name="completionCondition" value="context.approvalStatus === 'REJECTED'"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:multiInstanceLoopCharacteristics>
</bpmn:callActivity>
Use Cases for Sequential:
- Approval chains (escalation hierarchy)
- Rate-limited API calls
- Dependent operations
- State-dependent processing
Accessing Multi-Instance Context
Inside the subprocess, access iteration information:
// In subprocess script
logger.info('Processing item ' + (context.loopCounter + 1) + ' of ' + context.nrOfInstances);
// Parent context variables
var currentItem = currentOrder; // From elementVariable
// Iteration metadata
var index = context.loopCounter; // 0-based index
var total = context.nrOfInstances; // Total iterations
var completed = context.nrOfCompleted; // Completed so far
Result Aggregation
Results are automatically collected:
// After multi-instance completes, in parent process
var results = context.orderResults; // From aggregationVariable
logger.info('Total orders: ' + results.length);
// Analyze results
var successful = results.filter(r => r.processedOrder.success === true);
var failed = results.filter(r => r.processedOrder.success === false);
logger.info('Successful: ' + successful.length);
logger.info('Failed: ' + failed.length);
context.batchSummary = {
total: results.length,
successful: successful.length,
failed: failed.length,
successRate: (successful.length / results.length * 100).toFixed(2) + '%'
};
Callback Mechanism (Phase 3)
When parent process calls a subprocess, it automatically waits for completion using the callback system.
How Callbacks Work
-
Subprocess Started:
- Parent creates subprocess instance
- Callback registered in
CallbackRegistrytable - Parent status:
Waiting, callback type:Subprocess
-
Subprocess Executes:
- Subprocess runs independently
- Parent process state saved in database
-
Subprocess Completes:
- Callback detected in
CallbackRegistry - Parent process automatically resumed
- Output mapping applied
- Parent continues from next task
- Callback detected in
Database Callback Entry
-- CallbackRegistry table entry
INSERT INTO CallbackRegistry (
ProcessInstanceId, -- Parent instance ID
CallbackType, -- 'Subprocess'
SubprocessInstanceId, -- Child instance ID
ParentTaskId, -- Call Activity ID
Status -- 'Pending'
)
Process State During Wait
Implementation details removed for security.
Contact support for implementation guidance.
Async Boundaries (Phase 2)
Execute subprocess call in background:
<bpmn:callActivity id="GenerateReport"
name="Generate Monthly Report"
calledElement="ReportGenerationProcess"
camunda:asyncBefore="true">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="asyncBoundary" value="true"/>
<custom:property name="asyncBoundaryResponse" value="{
"message": "Report generation started",
"jobId": "context.reportJobId"
}"/>
<custom:property name="inputMapping" value="{
"month": "context.month",
"year": "context.year"
}"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:callActivity>
Behavior:
- API returns immediately with
asyncBoundaryResponse - Subprocess executes in background
- Parent process continues when subprocess completes
Error Handling (Phase 5)
Handle subprocess errors with boundary events:
<bpmn:callActivity id="ProcessPayment"
name="Process Payment"
calledElement="PaymentProcessingWorkflow">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="inputMapping" value="{
"accountId": "context.accountId",
"amount": "context.amount"
}"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:callActivity>
<!-- Catch subprocess errors -->
<bpmn:boundaryEvent id="PaymentError"
attachedToRef="ProcessPayment"
cancelActivity="true">
<bpmn:errorEventDefinition errorRef="PaymentError" />
</bpmn:boundaryEvent>
<!-- Error handler -->
<bpmn:scriptTask id="HandlePaymentError" name="Handle Payment Error">
<bpmn:script>
var error = context._lastError;
logger.error('Payment subprocess failed: ' + error.errorMessage);
logger.error('Error code: ' + error.errorCode);
// Retry logic or fallback
if (error.errorCode === 'INSUFFICIENT_FUNDS') {
// Handle specific error
context.paymentStatus = 'DECLINED';
context.declineReason = 'Insufficient funds';
} else {
// Generic error handling
context.paymentStatus = 'ERROR';
context.errorDetails = error.errorMessage;
}
</bpmn:script>
</bpmn:scriptTask>
<bpmn:error id="PaymentError" errorCode="PAYMENT_ERROR" />
Subprocess Error Propagation:
- If subprocess throws
BpmnError, parent boundary event catches it - Error context available in
context._lastError - Parent can retry, use fallback, or escalate
Complete Example: Multi-Stage Approval
<bpmn:process id="LoanApprovalOrchestration" name="Loan Approval">
<bpmn:startEvent id="Start" name="Loan Application"/>
<!-- Call validation subprocess -->
<bpmn:callActivity id="ValidateApplication"
name="Validate Application"
calledElement="LoanValidationProcess">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="inputMapping" value="{
"applicantId": "context.applicantId",
"loanAmount": "context.loanAmount",
"loanType": "context.loanType"
}"/>
<custom:property name="outputMapping" value="{
"validationResult": "subprocess.result",
"creditScore": "subprocess.creditScore",
"riskLevel": "subprocess.riskLevel"
}"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:callActivity>
<!-- Handle validation errors -->
<bpmn:boundaryEvent id="ValidationFailed"
attachedToRef="ValidateApplication">
<bpmn:errorEventDefinition errorRef="ValidationError"/>
</bpmn:boundaryEvent>
<!-- Decision: Which approval level? -->
<bpmn:exclusiveGateway id="CheckAmount" name="Amount?"/>
<!-- Small loans: Single manager approval -->
<bpmn:callActivity id="ManagerApproval"
name="Manager Approval"
calledElement="ManagerApprovalProcess">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="inputMapping" value="{
"loanDetails": "context",
"validationResult": "context.validationResult"
}"/>
<custom:property name="outputMapping" value="{
"approvalStatus": "subprocess.status",
"approvedBy": "subprocess.approver"
}"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:callActivity>
<!-- Large loans: Multi-instance approval chain -->
<bpmn:callActivity id="SeniorApprovalChain"
name="Senior Approval Chain"
calledElement="SeniorApprovalProcess">
<bpmn:multiInstanceLoopCharacteristics isSequential="true">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="collection" value="context.approvers"/>
<custom:property name="elementVariable" value="currentApprover"/>
<custom:property name="aggregationVariable" value="approvalResults"/>
<custom:property name="completionCondition" value="subprocess.status === 'REJECTED'"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:multiInstanceLoopCharacteristics>
<bpmn:extensionElements>
<custom:properties>
<custom:property name="inputMapping" value="{
"loanDetails": "context",
"approverId": "currentApprover.id",
"approverLevel": "currentApprover.level"
}"/>
<custom:property name="outputMapping" value="{
"status": "subprocess.status",
"comments": "subprocess.comments"
}"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:callActivity>
<!-- Aggregate approval results -->
<bpmn:scriptTask id="AggregateApprovals" name="Aggregate Results">
<bpmn:script>
var allApproved = context.approvalResults.every(r => r.status === 'APPROVED');
var anyRejected = context.approvalResults.some(r => r.status === 'REJECTED');
if (anyRejected) {
context.finalStatus = 'REJECTED';
} else if (allApproved) {
context.finalStatus = 'APPROVED';
} else {
context.finalStatus = 'PENDING';
}
logger.info('Final approval status: ' + context.finalStatus);
</bpmn:script>
</bpmn:scriptTask>
<bpmn:endEvent id="End" name="Approval Complete"/>
</bpmn:process>
Best Practices
✅ Do This
<!-- ✅ Clear subprocess naming -->
<bpmn:callActivity id="ValidateCustomer"
name="Validate Customer"
calledElement="CustomerValidationProcess"/>
<!-- ✅ Explicit input/output mapping -->
<custom:property name="inputMapping" value="{
"customerId": "context.customerId"
}"/>
<!-- ✅ Store result for later use -->
<custom:property name="resultVariable" value="validationResult"/>
<!-- ✅ Add error boundaries -->
<bpmn:boundaryEvent id="ValidationError" attachedToRef="ValidateCustomer">
<bpmn:errorEventDefinition/>
</bpmn:boundaryEvent>
⌠Don't Do This
<!-- ⌠Generic naming -->
<bpmn:callActivity id="Task1"
name="Task1"
calledElement="Process1"/>
<!-- ⌠No mapping (subprocess won't have data) -->
<bpmn:callActivity id="DoWork" calledElement="WorkProcess">
<!-- Missing inputMapping -->
</bpmn:callActivity>
<!-- ⌠No error handling -->
<!-- Missing boundary event for error recovery -->
Performance Considerations
| Scenario | Performance | Recommendation |
|---|---|---|
| Single subprocess call | ~100ms overhead | ✅ Use freely |
| Multi-instance (10 items) | ~500ms-1s | ✅ Use parallel mode |
| Multi-instance (100 items) | ~2-5s | ✅ Use parallel, consider batching |
| Multi-instance (1000+ items) | ~10s+ | âš ï¸ Consider pagination or async |
| Nested subprocesses (3 levels) | Cumulative overhead | âš ï¸ Flatten if possible |
Related Documentation
- Multi-Instance Overview - Detailed multi-instance guide
- Callbacks - Callback mechanism details
- Async Boundaries - Background execution
- Error Handling - Subprocess error handling
- Script Task - For inline logic instead of subprocess
Features Used:
- Core: Call Activity
- Phase 1: Multi-Instance Subprocess
- Phase 2: Async Boundaries
- Phase 3: Callbacks
- Phase 5: Error Handling
Status: ✅ Production Ready
Version: 2.0
Last Updated: January 2026