Skip to main content

Tasks

Tasks are the units of work in a process. The engine supports the standard BPMN task types — service, user, script, business-rule, send, receive, manual, and generic — and adds runtime metadata via the quantum: extension namespace.

Common attributes

AttributeNotes
idRequired, unique within the process
nameOptional display name
defaultID of an outgoing flow to take when every other outgoing flow's condition evaluates false. See Sequence Flow → Default flows for the full evaluation model
isForCompensationtrue marks the task as a compensation handler — it never runs on a normal sequence flow and must be linked to a compensation boundary event by an <association>. The validator rejects any isForCompensation="true" activity that has incoming sequence flows

Most tasks accept these extensions:

ExtensionPurpose
quantum:taskDefinitiontype (worker type string) and optional retries
quantum:ioMappingVariable mappings — see Common extensions
quantum:taskHeadersStatic headers passed to the worker — see Common extensions

Standard loop and multi-instance loop characteristics can be attached to most tasks — see Structure → Loops.


Service task

Delegates work to an external worker identified by a task type string. The engine creates a job for the configured type, hands it to a polling worker, and waits for the worker to complete or fail it.

Required configuration

AttributeNotes
quantum:taskDefinition typeWorker type string. Required — without it, deployment fails
quantum:taskDefinition retriesOptional retry attempts on failure (e.g. "3")

Example

<bpmn:serviceTask id="charge-card" name="Charge Credit Card">
<bpmn:extensionElements>
<quantum:taskDefinition type="payment-worker" retries="3" />
<quantum:ioMapping>
<quantum:input source="=orderId" target="order_id" />
<quantum:input source="=totalAmount" target="amount" />
<quantum:output source="=result.transactionId" target="transactionId" />
</quantum:ioMapping>
<quantum:taskHeaders>
<quantum:header key="currency" value="USD" />
</quantum:taskHeaders>
</bpmn:extensionElements>
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
</bpmn:serviceTask>

For details on writing workers, see External workers.

Validation

CheckSeverity
quantum:taskDefinition type is missing or emptyError

User task

Requires a human to complete the work. The engine evaluates the assignment fields, registers the task with the platform's user-task system, and waits for an external completion signal.

Configuration

ExtensionNotes
quantum:assignmentDefinition assignee="..."Single assigned user
quantum:assignmentDefinition candidateGroups="..."Comma-separated list of groups who can claim the task
quantum:assignmentDefinition candidateUsers="..."Comma-separated list of candidate users
quantum:formDefinition formKey="..."Form key used by the UI to render the task
quantum:formDefinition formId="..."Form definition ID
quantum:taskDefinition type="..."Optional, only relevant for custom user-task backends

A user task can carry an error boundary event. Completing the task with an error code routes the token through the boundary instead of the normal outgoing flow.

Example

<bpmn:userTask id="approve-request" name="Approve Request">
<bpmn:extensionElements>
<quantum:assignmentDefinition assignee="manager" candidateGroups="approvers, leads" />
<quantum:formDefinition formKey="approve-form" />
<quantum:ioMapping>
<quantum:input source="=requestId" target="request_id" />
<quantum:output source="=approved" target="isApproved" />
</quantum:ioMapping>
</bpmn:extensionElements>
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
</bpmn:userTask>

Script task

Executes an inline script in the engine. FEEL is the supported script language.

Attributes

AttributeNotes
scriptFormatSet to "feel"

The script body goes inside a <script> child element. Output mappings can pull values out of the script's local scope and write them to the parent scope.

Example

<bpmn:scriptTask id="calculate-total" name="Calculate Total" scriptFormat="feel">
<bpmn:extensionElements>
<quantum:ioMapping>
<quantum:output source="=total" target="orderTotal" />
</quantum:ioMapping>
</bpmn:extensionElements>
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
<bpmn:script>{ total: sum(items.price) }</bpmn:script>
</bpmn:scriptTask>

Business rule task

Invokes a decision and stores the result in a process variable. Two modes are supported, mirroring the send-task shape: embedded DMN evaluation (the engine runs the rule) or external worker dispatch (a worker performs the evaluation, e.g. against a remote rules engine).

Execution modes

ModeWhen activeBehavior
Embedded DMNquantum:calledDecision decisionId="..." is setEngine evaluates the named DMN decision synchronously and stores the result in resultVariable
External workerquantum:taskDefinition type="..." is set, no calledDecisionEngine creates a job — same semantics as a service task (retries, headers, boundary events, I/O mappings). The worker performs the rule evaluation and returns the result

At least one mode must be configured. Setting both is a warning: the external-worker dispatch wins and calledDecision is ignored.

Required configuration — embedded DMN

ExtensionNotes
quantum:calledDecision decisionId="..."Required. ID of the DMN decision to evaluate
quantum:calledDecision resultVariable="..."Required. Variable name to store the decision output
quantum:calledDecision bindingType="..."Optional. "" or "latest" (default) picks the newest deployed version, "version" pins to a specific integer version.
quantum:calledDecision version="N"Required when bindingType="version". Positive integer, rejected at deploy if missing, zero, or negative

Input mappings prepare the input data the decision expects, output mappings extract fields from the decision result for downstream use.

Example

<bpmn:businessRuleTask id="check-eligibility" name="Check Eligibility">
<bpmn:extensionElements>
<quantum:calledDecision decisionId="eligibility-rules" resultVariable="eligibilityResult" />
<quantum:ioMapping>
<quantum:input source="=customerAge" target="age" />
<quantum:input source="=annualIncome" target="income" />
<quantum:output source="=eligibilityResult.eligible" target="isEligible" />
</quantum:ioMapping>
</bpmn:extensionElements>
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
</bpmn:businessRuleTask>

Example — pinned to a specific version

<bpmn:businessRuleTask id="check-eligibility-pinned" name="Check Eligibility (v3)">
<bpmn:extensionElements>
<quantum:calledDecision decisionId="eligibility-rules" resultVariable="eligibilityResult" bindingType="version" version="3" />
</bpmn:extensionElements>
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
</bpmn:businessRuleTask>

Example — external worker

<bpmn:businessRuleTask id="check-eligibility-remote" name="Check Eligibility (remote)">
<bpmn:extensionElements>
<quantum:taskDefinition type="rules-worker" retries="2" />
<quantum:ioMapping>
<quantum:input source="=customerAge" target="age" />
<quantum:input source="=annualIncome" target="income" />
<quantum:output source="=result.eligible" target="isEligible" />
</quantum:ioMapping>
</bpmn:extensionElements>
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
</bpmn:businessRuleTask>

Validation

CheckSeverity
Neither quantum:calledDecision nor quantum:taskDefinition is setError
quantum:calledDecision is set but resultVariable is missingError
bindingType="version" without a positive integer versionError
Both quantum:calledDecision and quantum:taskDefinition are setWarning (taskDefinition wins, calledDecision is ignored)

Send task

Emits a message and advances. Operates in one of two modes depending on what extensions are present.

Execution modes

ModeWhen activeBehavior
Direct publishmessageRef is set, no quantum:taskDefinitionResolves the message name from the global declaration, publishes the message with the current scope variables, advances immediately
External jobquantum:taskDefinition type="..." is setCreates a job for a worker — same semantics as a service task (retries, headers, boundary events, I/O mappings). Waits for worker completion

If neither is set, the task is a no-op pass-through. If both are set, the worker mode wins.

Examples

<!-- Direct publish -->
<bpmn:sendTask id="send-order" name="Notify Order Placed" messageRef="Msg_OrderPlaced">
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
</bpmn:sendTask>

<!-- Worker mode (e.g. SMTP, SMS) -->
<bpmn:sendTask id="notify-customer" name="Send Confirmation Email">
<bpmn:extensionElements>
<quantum:taskDefinition type="email-sender" retries="2" />
<quantum:ioMapping>
<quantum:input source="=customerEmail" target="to" />
<quantum:input source="=orderId" target="order_id" />
</quantum:ioMapping>
<quantum:taskHeaders>
<quantum:header key="template" value="order-confirmation" />
</quantum:taskHeaders>
</bpmn:extensionElements>
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
</bpmn:sendTask>

Receive task

Blocks until a correlated message arrives. The message is referenced by messageRef. If a buffered message is already available it fires immediately, otherwise the task waits.

messageRef is required — an empty message name produces a subscription that no publish can match, leaving the task waiting forever. The validator rejects receive tasks without a messageRef.

Example

<bpmn:receiveTask id="wait-for-payment" name="Wait for Payment" messageRef="Msg_Payment">
<bpmn:extensionElements>
<quantum:ioMapping>
<quantum:output source="=amount" target="paidAmount" />
</quantum:ioMapping>
</bpmn:extensionElements>
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
</bpmn:receiveTask>

Validation

CheckSeverity
messageRef is missing or emptyError
messageRef references an unknown <message> definitionError

Manual task

Represents work performed entirely outside the engine, with no automated execution.

⚠️ Pass-through at runtime. The engine applies I/O mappings on the manual task and advances the token immediately — no job is created and no external completion is awaited, so the process is not blocked. The modeler surfaces a warning on every manual task at design time. If you need work that blocks the process and dispatches to a worker, use a service task, for human work, use a user task. The generic task below shares the same pass-through behaviour.

Example

<bpmn:manualTask id="physical-review" name="Physical Document Review">
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
</bpmn:manualTask>

Generic task

<task> with no specific type. Acts as a pass-through activity — same runtime behaviour as a manual task: the engine applies input and output mappings and advances the token immediately. No job is created and no external completion is awaited. Use it as a labelled checkpoint in the model when the work category isn't relevant and you don't need execution to block on it.

Example

<bpmn:task id="checkpoint" name="Manual Review Pending">
<bpmn:incoming>f1</bpmn:incoming>
<bpmn:outgoing>f2</bpmn:outgoing>
</bpmn:task>

FAQ

What's the difference between service task, user task, script task, and business rule task in BPMN?

A service task delegates work to an external worker over a job queue — typical for calls to other systems (payment APIs, email senders, integrations). A user task waits for a human assignee or candidate group to complete it through the user-task UI or API. A script task evaluates a FEEL expression inline in the engine and exposes its result through output mappings. A business rule task invokes a DMN decision (either embedded, evaluated by the engine, or via an external worker) and stores the result in a named variable. All four share the same I/O mapping, retry, header, and boundary-event mechanisms.

What's the difference between a send task and a message throw event?

Both publish a message. The choice is structural: a send task is an activity, so it can carry boundary events (e.g. a timer boundary to cap how long an external send is allowed), multi-instance markers (publish a message per item in a collection), and I/O mappings in the same shape as service tasks. A message intermediate throw event is leaner — it advances immediately on direct dispatch — and composes more naturally inside event chains. Both also support an external-worker mode (quantum:taskDefinition type="...") that defers the actual publish to a worker, choose the send task when worker-mode dispatch is the primary use, the throw event for inline engine publishes.

What's the difference between a receive task and an intermediate message catch event?

Both wait for a correlated message and share identical correlation semantics — they subscribe under name plus correlation value and check the message buffer on entry. The structural difference matters: a receive task is an activity, so it can carry boundary events (a timer boundary to time out the wait is the typical pattern) and multi-instance markers. An intermediate message catch event is leaner and composes naturally inside event-based gateway races. Use a receive task when you need a timeout or want to fan out one wait per item, use a catch event inside event-based gateways or compact event chains.

What scripting language does a BPMN script task support in QuantumBPM?

FEEL only. Set scriptFormat="feel" and put the FEEL expression inside a <script> child element. The expression is evaluated against the task's scope (input-mapped variables plus the surrounding scope chain), and <quantum:output> mappings can extract values from the script's local scope into the parent scope. Other languages — JavaScript, Groovy, Python, JUEL — are not supported. If you need imperative logic, model the work as a service task with a worker.

How does a business rule task call a DMN decision?

Add a <quantum:calledDecision decisionId="..." resultVariable="..."/> extension that names the DMN decision and the variable to store the result. Input mappings prepare the inputs the decision expects from scope variables, output mappings extract fields from the result for downstream use. The version is controlled by bindingType: the default ("" or "latest") picks the newest deployed version, and "version" pins to a specific integer via the version attribute. "versionTag" and "deployment" are rejected at deployment. Alternatively, set quantum:taskDefinition type="..." to dispatch the evaluation to an external worker — the engine creates a job exactly like a service task and the worker returns the result.

Are BPMN manual tasks and generic tasks executable in QuantumBPM?

Both are pass-through at runtime. The engine applies input and output mappings on the task and advances the token immediately — no job is created and no external completion is awaited, so neither blocks the process the way a service task or user task does. The modeler raises a design-time warning on manual tasks to make this visible, the generic <task> element shares the same pass-through behaviour. If you need work that blocks the process and dispatches to a worker, use a service task. For human work, use a user task. For messaging, use a send task (publish) or receive task (wait).

What does `isForCompensation="true"` do on a BPMN task?

It marks the task as a compensation handler. Handlers do not run on normal sequence flows — they are reachable only when a compensation event is thrown for the activity they're associated with. A compensation handler must have no incoming sequence flow (the engine rejects the model otherwise) and must be linked to a compensation boundary event on the activity it compensates via an <association> element. A typical use is undoing a side effect — "cancel hotel booking" as the compensation handler for "book hotel" — when a later step fails and triggers compensation upstream.

How do task retries work in QuantumBPM?

Set quantum:taskDefinition retries="N" on the task. When the worker fails the job, the engine decrements the retry counter and re-creates the job for the next worker poll. When retries reach zero, the failure is final and either propagates to an error boundary event on the task (if one is configured with a matching errorRef) or fails the process instance. Retries apply to every task type that actually creates a job — service tasks, send tasks in worker mode, business rule tasks in worker mode, and user tasks with a custom backend. They do not apply to manual tasks or generic task elements, which are pass-through and never reach a worker.

What's the difference between sequential and parallel multi-instance tasks?

isSequential="true" runs iterations one at a time — each must complete before the next starts. isSequential="false" (the default) starts every iteration simultaneously in separate scopes. Both expose the same instance attributes — numberOfInstances, numberOfActiveInstances, numberOfCompletedInstances, numberOfTerminatedInstances, plus a 1-based loopCounter on each individual instance — for use in FEEL expressions inside the activity and inside the optional completionCondition that can exit the loop early (remaining active instances are then cancelled and counted as terminated). Use sequential when iterations must observe each other's side effects, parallel for independent work that should fan out.

How does input and output mapping work on a BPMN task?

<quantum:ioMapping> reshapes variables at the boundary of a task. Each <quantum:input source="..." target="..."/> evaluates a FEEL expression against the surrounding scope and writes the result into the task's local scope under the target name — useful for renaming variables or extracting nested values before the worker, script, or decision sees them. Each <quantum:output source="..." target="..."/> does the reverse after the task completes: it evaluates a FEEL expression against the task's local scope (including its result) and writes the value back into the parent scope under the target name. We strongly recommend declaring explicit input and output mappings on every task — they document the task's contract, prevent accidental variable coupling between distant parts of the process, and let you rename or restructure variables in one task without breaking unrelated downstream tasks. The engine does not enforce mappings, without them every scope variable flows into the task and the task's result is merged back into the parent scope as-is, which is best avoided in production models.