Skip to main content

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

PropertyTypeRequiredDescription
calledElementstringYesProcess definition ID to call
TaskTypestringYesMust be "CallActivity"
NamestringYesDisplay name

Optional Properties

PropertyTypeDefaultDescription
inputMappingJSONnullMap parent variables to subprocess
outputMappingJSONnullMap subprocess results to parent
resultVariablestringlastSubprocessResultVariable to store subprocess result
asyncBeforebooleanfalseExecute asynchronously (Phase 2)
isMultiInstancebooleanfalseExecute multiple times (Phase 1)
collectionexpressionnullArray for multi-instance execution
elementVariablestringitemVariable 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="{
&quot;customerId&quot;: &quot;context.customerId&quot;,
&quot;validationType&quot;: &quot;FULL&quot;
}"/>
<custom:property name="outputMapping" value="{
&quot;validationResult&quot;: &quot;subprocess.result&quot;,
&quot;validationErrors&quot;: &quot;subprocess.errors&quot;
}"/>
<custom:property name="resultVariable" value="customerValidation"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:callActivity>

How it works:

  1. Parent process reaches Call Activity
  2. New subprocess instance created with CustomerValidationProcess definition
  3. Input mapping transfers data from parent to subprocess
  4. Parent process waits for subprocess completion (callback mechanism)
  5. When subprocess completes, output mapping transfers results back
  6. Parent process continues

Input/Output Mapping

Input Mapping (Parent → Subprocess)

Transfer data from parent process to subprocess:

<custom:property name="inputMapping" value="{
&quot;subprocessVariable&quot;: &quot;context.parentVariable&quot;,
&quot;applicantId&quot;: &quot;context.customerId&quot;,
&quot;requestDate&quot;: &quot;context.applicationDate&quot;,
&quot;amount&quot;: &quot;context.loanAmount&quot;
}"/>

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="{
&quot;parentVariable&quot;: &quot;subprocess.subprocessVariable&quot;,
&quot;approvalStatus&quot;: &quot;subprocess.status&quot;,
&quot;approvalComments&quot;: &quot;subprocess.comments&quot;,
&quot;approvedBy&quot;: &quot;subprocess.approverId&quot;
}"/>

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="{
&quot;order&quot;: &quot;currentOrder&quot;,
&quot;processingMode&quot;: &quot;BATCH&quot;
}"/>
<custom:property name="outputMapping" value="{
&quot;processedOrder&quot;: &quot;subprocess.result&quot;
}"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:callActivity>

Multi-Instance Properties:

PropertyTypeRequiredDescription
collectionexpressionYesArray to iterate (e.g., context.orders)
elementVariablestringNoVariable name for current item (default: item)
aggregationVariablestringNoCollect all results (default: results)
completionConditionexpressionNoEarly termination condition
isSequentialbooleanNoSequential 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

  1. Subprocess Started:

    • Parent creates subprocess instance
    • Callback registered in CallbackRegistry table
    • Parent status: Waiting, callback type: Subprocess
  2. Subprocess Executes:

    • Subprocess runs independently
    • Parent process state saved in database
  3. Subprocess Completes:

    • Callback detected in CallbackRegistry
    • Parent process automatically resumed
    • Output mapping applied
    • Parent continues from next task

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

Code Removed

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="{
&quot;message&quot;: &quot;Report generation started&quot;,
&quot;jobId&quot;: &quot;context.reportJobId&quot;
}"/>
<custom:property name="inputMapping" value="{
&quot;month&quot;: &quot;context.month&quot;,
&quot;year&quot;: &quot;context.year&quot;
}"/>
</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="{
&quot;accountId&quot;: &quot;context.accountId&quot;,
&quot;amount&quot;: &quot;context.amount&quot;
}"/>
</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="{
&quot;applicantId&quot;: &quot;context.applicantId&quot;,
&quot;loanAmount&quot;: &quot;context.loanAmount&quot;,
&quot;loanType&quot;: &quot;context.loanType&quot;
}"/>
<custom:property name="outputMapping" value="{
&quot;validationResult&quot;: &quot;subprocess.result&quot;,
&quot;creditScore&quot;: &quot;subprocess.creditScore&quot;,
&quot;riskLevel&quot;: &quot;subprocess.riskLevel&quot;
}"/>
</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="{
&quot;loanDetails&quot;: &quot;context&quot;,
&quot;validationResult&quot;: &quot;context.validationResult&quot;
}"/>
<custom:property name="outputMapping" value="{
&quot;approvalStatus&quot;: &quot;subprocess.status&quot;,
&quot;approvedBy&quot;: &quot;subprocess.approver&quot;
}"/>
</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="{
&quot;loanDetails&quot;: &quot;context&quot;,
&quot;approverId&quot;: &quot;currentApprover.id&quot;,
&quot;approverLevel&quot;: &quot;currentApprover.level&quot;
}"/>
<custom:property name="outputMapping" value="{
&quot;status&quot;: &quot;subprocess.status&quot;,
&quot;comments&quot;: &quot;subprocess.comments&quot;
}"/>
</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="{
&quot;customerId&quot;: &quot;context.customerId&quot;
}"/>

<!-- ✅ 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

ScenarioPerformanceRecommendation
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

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