Variables
Variables are part of a process instance and represent the data of the instance.
A variable has a name and a JSON value. The visibility of a variable is defined by its variable scope.
When automating a process using BPMN or orchestrating human tasks, you can leverage the scope of these variables and customize how variables are merged into the process instance.
Variable names
The name of a variable can be any alphanumeric string including the _ symbol. For a combination of words, it's recommended to use the camelCase or the snake_case format. The kebab-case format is not allowed because it contains the operator -.
When accessing a variable in an expression, keep in mind the variable name is case-sensitive.
Restrictions of a variable name:
- It may not start with a number (e.g.
1stChoiceis not allowed; you can usefirstChoiceinstead). - It may not contain whitespaces (e.g.
order numberis not allowed; you can useorderNumberinstead). - It may not contain an operator (e.g.
+,-,*,/,=,>,?,.). - It may not be a literal (e.g.
null,true,false) or a keyword (e.g.function,if,then,else,for,between,instance,of,not). - It may not be longer than 492 characters (UTF-8 encoded).
Variable values
The value of a variable is stored as a JSON value. It can have one of the following types:
- String (e.g.
"John Doe") - Number (e.g.
123,0.23) - Boolean (e.g.
trueorfalse) - Array (e.g.
["item1" , "item2", "item3"]) - Object (e.g.
{ "orderNumber": "A12BH98", "date": "2020-10-15", "amount": 185.34}) - Null (
null)
Numbers are subject to the following numeric limits:
- Integer numbers are effectively limited to the 64‑bit integer range.
- Non‑integer numbers are stored as IEEE‑754 double‑precision values, which provide roughly 15–17 significant decimal digits rather than arbitrary BigDecimal precision.
If you need arbitrary-precision or very large numbers, consider storing them as strings or in an external data store instead of process variables.
Variable size limitation
The payload of a process instance is limited to 4 MB. This limit includes both process variables and workflow engine–internal data, so less than 4 MB is available for variables alone.
The effective limit depends on the operation. As a rule of thumb, ~1.5 MB is considered safe for commands or events that include variables, such as starting a process instance or completing a job. In these cases, the engine may append follow-up records that temporarily duplicate the variable payload within the same batch.
To avoid production issues, leave headroom below the limit—for example, target ≤1 MB—and validate with a production-like test case. If the payload size is uncertain, run a quick test to confirm behavior.
Regardless, we don't recommend storing much data in your process context. Refer to our best practice on handling data in processes.
Variable scopes
Variable scopes define the visibility of variables. The root scope is the process instance itself. Variables in this scope are visible everywhere in the process.
When the process instance enters a subprocess or an activity, a new scope is created. Activities in this scope can observe all variables of this and of higher scopes (i.e. parent scopes). However, activities outside of this scope can not observe the variables which are defined in this scope.
If a variable has the same name as a variable from a higher scope, it covers this variable. Activities in this scope observe only the value of this variable and not the one from the higher scope.
The scope of a variable is defined when the variable is created. By default, variables are created in the root scope.

This process instance has the following variables:
aandbare defined on the root scope and can be seen by Task A, Task B, and Task C.cis defined in the subprocess scope and can be seen by Task A and Task B.bis defined again on the activity scope of Task A and can be seen only by Task A. It covers the variablebfrom the root scope.
Variable propagation
When variables are merged into a process instance (e.g. on job completion, on message correlation, etc.) each variable is propagated from the scope of the activity to its higher scopes.
The propagation ends when a scope contains a variable with the same name. In this case, the variable value is updated.
If no scope contains this variable, it's created as a new variable in the root scope.

The job of Task B is completed with the variables b, c, and d. The variables b and c are already defined in higher scopes and are updated with the new values. Variable d doesn't exist before and is created in the root scope.
Variable propagation by BPMN element
What an element propagates to its parent scope when it completes depends on its type:
| BPMN element | What propagates on completion |
|---|---|
| Service, send, user, receive, script, and business rule tasks | The task result: the job result, correlated message payload, or evaluated expression. With an output mapping, only the mapped variables propagate; without one, the whole result propagates. |
| Embedded and event subprocesses | Nothing by default. Local variables are discarded when the subprocess completes unless an output mapping propagates them. |
| Call activity | By default, all variables of the child process instance, because propagateAllChildVariables is enabled. With an output mapping, only the mapped variables. If you disable propagateAllChildVariables and define no output mapping, nothing propagates. |
| Ad-hoc subprocess | Its output collection, if configured, plus any variables an output mapping propagates. Variables written by its activated activities stay local to each activation. |
| Multi-instance activity | Its output collection, if configured, pushed to the parent scope when the loop finishes. The loopCounter variable and the per-instance input element variable stay local to each instance. |
| Start, intermediate catch, and boundary events | The correlated event payload (message, signal, timer, or conditional), or the mapped variables if an output mapping is defined. |
| Throw and end events | Only the mapped variables where output mappings are supported, or, for message and signal variants backed by a job or connector, the job result. The none, link, and escalation variants propagate nothing without a mapping. The error, terminate, and compensation variants do not support output mappings and propagate nothing. |
A call activity that disables propagateAllChildVariables without defining an output mapping discards everything the child process produced. To return only selected child process variables to the caller, keep propagateAllChildVariables enabled and define an output mapping on the call activity.
An error end event does not propagate variables through output mappings. Any payload it carries travels to the catching boundary event or event subprocess instead.
Internal engine variables
Some variables are created and managed by the engine to control execution. They are scoped locally to the element instance that uses them and are not intended as process data:
loopCounter: the current iteration index inside a multi-instance activity. Read it within an iteration, but do not propagate it to higher scopes.- The multi-instance input element variable: the item assigned to the current iteration from the input collection. Its name is set by the
inputElementattribute, and it stays local to that iteration.
Avoid referencing these variables in output mappings. Propagating them beyond their intended scope can produce incorrect results.
Local variables
In some cases, variables should be set in a given scope, even if they don't exist in this scope before.
To deactivate the variable propagation, the variables are set as local variables. This means the variables are created or updated in the given scope, regardless if they existed in this scope before.
Input/output variable mappings
Input/output variable mappings can be used to create new variables or customize how variables are merged into the process instance.
Variable mappings are defined in the process as extension elements under ioMapping. Every variable mapping has a source and a target expression.
The source expression defines the value of the mapping. It usually accesses a variable of the process instance that holds the value. If the variable or nested property doesn't exist, the value resolves to null. The same applies if you do not provide a source.
The target expression defines where the value of the source expression is stored. It can reference a variable by its name or a nested property of a variable. If the variable or the nested property doesn't exist, it's created.
Variable mappings are evaluated in the defined order. Therefore, a source expression can access the target variable of a previous mapping.

Input mappings
| Source | Target |
|---|---|
customer.name | sender |
customer.iban | iban |
totalPrice | price |
orderId | reference |
Output mapping
| Source | Target |
|---|---|
status | paymentStatus |
Input mappings
Input mappings can be used to create new variables. They can be defined on service tasks, script tasks, business rule tasks, call activities, user tasks, send tasks, subprocesses, event subprocesses, and ad-hoc subprocesses. Support depends on the element type. See the element's own page for details.
When an input mapping is applied, it creates a new local variable in the scope where the mapping is defined.
You can use expressions or static values for input mappings. You can leave the source empty to map the target variable to null.
For string literals containing escaped characters (e.g., a newline character \n), the string is returned in its original form as expected (no double escaping is applied).
Examples:
| Process variables | Input mappings | New variables |
|---|---|---|
orderId: "order-123" | source: =orderIdtarget: reference | reference: "order-123" |
customer:{"name": "John"} | source: =customer.nametarget: sender | sender: "John" |
customer: "John"iban: "DE456" | source: =customertarget: sender.namesource: =ibantarget: sender.iban | sender: {"name": "John", "iban": "DE456"} |
| - | source: "Peter"target: sender | sender: "Peter" |
customer:{"name": "John"} | source: (not provided) target: customer | customer: null |
Output mappings
Output mappings can be used for several purposes:
- To customize how variables are merged into the process instance.
- They can be defined on most tasks (service, send, user, receive, script, and business rule tasks), embedded and event subprocesses, call activities, and ad-hoc subprocesses.
- They can also be defined on many events, including message, signal, timer, and conditional catch events, boundary events, and start events.
If one or more output mappings are defined, the results variables are set as local variables in the scope where the mapping is defined. Then, the output mappings are applied to the variables and create new variables in this scope. The new variables are merged into the parent scope. If there is no mapping for a job/message variable, the variable is not merged.
This can lead to a case where some variables with an output mapping are merged into the parent scope, and others without an output mapping are not merged.
If no output mappings are defined, the behavior depends on the element. Tasks and events that produce a result (a job result, or a correlated message or signal payload) merge that whole result into the parent scope. Subprocesses and events that produce no result, such as none, link, escalation, and compensation events, propagate nothing; their local variables are discarded when the scope is left. For an overview across all element types, see variable propagation by BPMN element.
In the case of a subprocess, the behavior is different. There are no results variables to be merged. However, output mappings can be used to propagate local variables of the subprocess to higher scopes. By default, all local variables are removed when the scope is left.
Examples:
| Results variables | Output mappings | Process variables |
|---|---|---|
status: "Ok" | source: =statustarget: paymentStatus | paymentStatus: "OK" |
result: {"status": "Ok", "transactionId": "t-789"} | source: =result.statustarget: paymentStatussource: =result.transactionIdtarget: transactionId | paymentStatus: "Ok"transactionId: "t-789" |
An output mapping target that contains a period (for example, order.status) updates only the final property and merges it into the existing variable at that path, leaving sibling properties untouched. This is supported, but use it deliberately, as it modifies a property of an existing variable rather than replacing the whole variable. For details, see nested variables in mappings.
Nested variables in mappings
Input and output mappings build nested target paths in opposite ways:
- Input mappings treat the
targetas a full path and write the entire nested object at that path, replacing any value already there. - Output mappings write only the final property (the leaf) of the
targetpath and merge it into the existing structure, preserving sibling properties that are already present.
For example, an input mapping with target order replaces the whole order variable, while an output mapping with target order.status sets only status inside an existing order variable and leaves its other properties unchanged.
Context variable
A context variable is a reserved variable that describes the context of a task. It can group variables together to provide a detailed description of the task or offer more descriptive data about it.
The reserved variable name for a context variable is taskContextDisplayName. This name is reserved exclusively for this purpose and should not be used for other variables.
Context variables are not supported in Tasklist V2. See migration from V1 to V2.
Example:
| Input variable | Example |
|---|---|
taskContextDisplayName | This is a context variable example |
The data from the variable will be shown on the task tile, as shown in the example below:
