What is the lifetime of task variables / how to access task variables after a task has been completed?

4,600 views
Skip to first unread message

kwma...@gmail.com

unread,
Feb 3, 2015, 2:02:15 AM2/3/15
to camunda-...@googlegroups.com
Hello Camunda Community,

at the moment I am struggling to find out how to access previously set task variables on a task that has been completed. What I tried is:

final Map<String, Object> taskVariables = getProcessEngine().getTaskService().getVariables(taskInstanceId);
--> Only works on created task that have not been completed

getProcessEngine().getHistoryService().createHistoricVariableInstanceQuery().taskIdIn(taskInstanceId).list();
--> always results in empty list independent of task completion

The task variables I need on task level are the approval status of a task and a comment the approver has to give when completing the (approval) task.

Regards
Kristian

thorben....@camunda.com

unread,
Feb 3, 2015, 3:59:48 AM2/3/15
to camunda-...@googlegroups.com, kwma...@gmail.com
Hi Kristian,

Local task variables (i.e. set by TaskService#setVariableLocal) are removed after the task itself is removed from the runtime database (i.e. on task completion). If you want to access the variable value within the process at a following task, you should use TaskService#setVariable or RuntimeService#setVariable. The difference to #setVariableLocal is that the variable is set on the top-most scope in the hierarchy, which is typically the process instance. So when the task is removed, the variable is still there and can be retrieved with RuntimeService#getVariable. See [1] for details on variable scopes. You can also use an inputOutput mapping that transfers task local variables to a higher scope when the task completes [2].

If local task variables are indeed what you want, you can still get them from the history with the query you described. In that case however, your engine's history level has to be set accordingly. Did you set it to the level AUDIT or FULL (cf [2])?

Cheers,
Thorben

[1] http://docs.camunda.org/latest/guides/user-guide/#process-engine-process-variables
[2] http://docs.camunda.org/latest/guides/user-guide/#process-engine-process-variables-inputoutput-variable-mapping
[3] http://docs.camunda.org/latest/guides/user-guide/#process-engine-history-and-audit-event-log-choosing-a-history-level

kwma...@gmail.com

unread,
Feb 3, 2015, 8:05:20 AM2/3/15
to camunda-...@googlegroups.com, kwma...@gmail.com
Hi Thorben,

thanks for your info, I was not of [3] and I will check if [2] does make sense.

I did not set / increase the history log level, so if the default is not high enough, this should be the explanation, why I don't get a result when working with the historyService on completed tasks.

However, there are still questionmarks on this topic for me.

E.g. in which scope will the variables placed when I pass them as part of task completion?:

getProcessEngine().getTaskService().complete(taskInstanceId, variables);

Also, what is the executionId when using

processEngine.getRuntimeService().getVariable(s)(executionId)

in terms of ProcessInstance (-> processInstanceId), Task (->? ), TaskInstance (-> id: did not work)? Does only the process instance id make sense here?

Thanks & Regards
Kristian

kwma...@gmail.com

unread,
Feb 3, 2015, 8:50:08 AM2/3/15
to camunda-...@googlegroups.com, kwma...@gmail.com
Hi Thorben,

as addition to my previous post, when I set task variables right before task completion like this:

getProcessEngine().getRuntimeService().setVariables(taskInstanceId, variables);
getProcessEngine().getTaskService().complete(taskInstanceId);

then it should be possible to access these variables at a point in time when the task is already completed and the process token is at another execution point like this:

getProcessEngine().getRuntimeService().getVariables(taskInstanceId);

What I do get is:

org.camunda.bpm.engine.exception.NullValueException: execution 15 doesn't exist: execution is null
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
at org.camunda.bpm.engine.impl.util.EnsureUtil.generateException(EnsureUtil.java:283)
at org.camunda.bpm.engine.impl.util.EnsureUtil.ensureNotNull(EnsureUtil.java:44)
at org.camunda.bpm.engine.impl.util.EnsureUtil.ensureNotNull(EnsureUtil.java:39)
at org.camunda.bpm.engine.impl.cmd.GetExecutionVariablesCmd.execute(GetExecutionVariablesCmd.java:52)
at org.camunda.bpm.engine.impl.cmd.GetExecutionVariablesCmd.execute(GetExecutionVariablesCmd.java:30)
at org.camunda.bpm.engine.impl.interceptor.CommandExecutorImpl.execute(CommandExecutorImpl.java:24)
at org.camunda.bpm.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:97)
at org.camunda.bpm.engine.spring.SpringTransactionInterceptor$1.doInTransaction(SpringTransactionInterceptor.java:42)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:130)
at org.camunda.bpm.engine.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:40)
at org.camunda.bpm.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:32)
at org.camunda.bpm.engine.impl.RuntimeServiceImpl.getVariablesTyped(RuntimeServiceImpl.java:152)
at org.camunda.bpm.engine.impl.RuntimeServiceImpl.getVariablesTyped(RuntimeServiceImpl.java:148)
at org.camunda.bpm.engine.impl.RuntimeServiceImpl.getVariables(RuntimeServiceImpl.java:144)
at org.camunda.bpm.engine.impl.RuntimeServiceImpl.getVariables(RuntimeServiceImpl.java:60)

Regards
Kristian



thorben....@camunda.com

unread,
Feb 3, 2015, 9:38:22 AM2/3/15
to camunda-...@googlegroups.com, kwma...@gmail.com
Hi Kristian,

Part [1] of the docs tries to introduce the concept of scopes and where variables are placed in the hierarchy of scopes.
I'll try to give a concrete example: Imagine you have the scope structure as in [2]. Every blue box is a scope, that means it can hold variables during its lifetime. Scopes are hierarchical, meaning that every scope has a parent and possibly many children. There are different kinds of scopes: Executions and tasks.

Tasks are units of work that have to be executed by humans. Their lifetime is restricted to the duration of the BPMN user task instance. In [2], two human tasks are active which represent one scope each. This means, they can hold variables as long as they exist (i.e. until completion).

Executions are like tokens, they move through the process graph. As a rule of thumb, you have one execution for every active, concurrent path of activities in a process. Furthermore, you have parent executions that group executions together that originate from one path. In the process of [2], when the process is started, there is only one execution for the start event. When it reaches the parallel fork, it creates two child executions, Execution 2 and Execution 3, one for each outgoing branch of the parallel fork. The process instance is an execution itself and always the root execution in the hierarchy. Like tasks, executions are variable scopes, so they can hold variables.

Now you can use the process engine API to set variables.

Some examples:
- taskService.setVariablesLocal(taskId, variables): "Local" means the variable is always set on the task that is provided by "taskId". Not in any of the higher scopes. You can only use this method for tasks, not for executions.
- taskService.setVariables(taskId, variables): Each variable contained in "variables" is set in the highest possible scope. Highest possible means that for each variable, the scopes are traversed from the task scope upwards and either a variable of the same name is updated or a new variable instance is created in the topmost scope, which is the process instance. The latter is the regular case.

- runtimeService.setVariablesLocal(executionId, variables): The same as taskService.setVariableLocal but for executions instead of tasks. That is also why you get an exception when you supply a task id as argument.
- runtimeService.setVariables(executionId, variables): again the parallel construct to taskService.setVariables

- taskService.complete(taskId, variables): The same as runtimeService.setVariables for the execution that created the task. In addition, it tells the process engine to complete the task and continue process execution. Variables you provide here do not get lost as they are stored in an execution.

Now the getter methods:
- runtimeService.getVariables(executionId): Returns all variables that are visible from the execution provided. A variable is "visible", if it is defined in the same scope or on a higher scope. If you pass the process instance id as an argument, all variables are returned that are set on the process instance scope.
- taskService.getVariables(taskId): Similar to the one mentioned before, however starting at the task's scope. All variables are returned that are defined on the task or in any of its parent scopes.

So what is the hierarchal concept good for?

Consider [2]. You can separate variables between different paths of process execution. For example, you can define a variable with the same name but different value for Execution 2 and Execution 3. Still, you can share variables between both by defining them on Execution 1.

I hope this makes sense and clarifies the concept a little.

Cheers,
Thorben

[1] http://docs.camunda.org/latest/guides/user-guide/#process-engine-process-variables-variable-scopes-and-variable-visibility
[2] http://docs.camunda.org/latest/guides/user-guide/assets/img/variables-4.png

Ingo Richtsmeier

unread,
Feb 4, 2015, 3:37:08 AM2/4/15
to camunda-...@googlegroups.com
Hi Thorben,

thank you very much this post. It's very helpful!

Kind Regards, Ingo

kwma...@gmail.com

unread,
Feb 5, 2015, 5:02:24 AM2/5/15
to camunda-...@googlegroups.com, kwma...@gmail.com
Hi Torben,

thanks a lot for detailed explanation of the concept about viarable scopes. I think I am getting to understand, but I am still a little confused about using variables with the api, especially when the saved variable shall have long lifetime.

Concerning lifetime of "process data" (thinkgs like process start time, process end time, process start user, task start user, task start, task end, task user switch, task variables (approval decision and comment of user)) we have the requirement to have it accessible (via API + GUI (custom?)) X months even after the process instance has been finished. The X depends on the process domain and project specific requirements.

When I have understood your explanation and the docu correct, this requirement is possible to archieve with using tasklocal variables and by increasing history log level to audit.

Regards
Kristian

thorben....@camunda.com

unread,
Feb 5, 2015, 5:18:37 AM2/5/15
to camunda-...@googlegroups.com, kwma...@gmail.com
Hi Kristian,

Your understanding is correct. If you want to later access the task local variables at process runtime (e.g. from a follow-up task), I'd advise not to store them as task-local variables. Of course you can also get them from the history at runtime but history may have worse performance because it constantly grows over time (unless you archive it yourself from time to time) and has typically a greater volume than the runtime tables at any point in time. The runtime tables contain only data that is associated with running process instances.

If you need the task local variables only for reporting purposes probably months after the process instance has finished, then it's perfectly fine.

Regarding API: If you do not have something like parallel branches in your process that have to work with variables of the same name but different values, the easiest way is to use the methods that set non-local variables. These variables are then valid for the entire lifetime of the process instance and you do not have to worry much about the hierarchy of scopes. Then, you can access the variables from any follow-up task in the process. The local-methods are useful when you do not want that behavior.

Cheers,
Thorben
Reply all
Reply to author
Forward
0 new messages