flowable/flowable-engine · error · ActivitiException

lazy loading outside command context

Error message

lazy loading outside command context

What it means

VariableScopeImpl.ensureVariableInstancesInitialized initializes the scope's variable map by loading all variable instances from the database, which requires an active CommandContext. When a variable-scope entity (Execution/Task) is accessed outside engine command execution, initialization is impossible and the engine throws this ActivitiException to prevent silent data corruption. This is the shared guard behind lazy variable loading for all scopes.

Source

Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/persistence/entity/VariableScopeImpl.java:68

    protected Map<String, VariableInstance> transientVariabes;

    protected ELContext cachedElContext;

    protected String id;

    protected abstract List<VariableInstanceEntity> loadVariableInstances();

    protected abstract VariableScopeImpl getParentVariableScope();

    protected abstract void initializeVariableInstanceBackPointer(VariableInstanceEntity variableInstance);

    protected void ensureVariableInstancesInitialized() {
        if (variableInstances == null) {
            variableInstances = new HashMap<>();

            CommandContext commandContext = Context.getCommandContext();
            if (commandContext == null) {
                throw new ActivitiException("lazy loading outside command context");
            }
            List<VariableInstanceEntity> variableInstancesList = loadVariableInstances();
            for (VariableInstanceEntity variableInstance : variableInstancesList) {
                variableInstances.put(variableInstance.getName(), variableInstance);
            }
        }
    }

    @Override
    public Map<String, Object> getVariables() {
        return collectVariables(new HashMap<>());
    }

    @Override
    public Map<String, VariableInstance> getVariableInstances() {
        return collectVariableInstances(new HashMap<>());
    }

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Access variables through the public services (runtimeService.getVariable / taskService.getVariable) which run inside commands
  2. Wrap custom logic in managementService.executeCommand(...) to establish a CommandContext
  3. Do not cache or serialize variable-scope entities; re-fetch them per command
  4. In async code, pass ids/primitive values instead of entity references

Example fix

// before
Object v = detachedExecution.getVariable("status"); // throws
// after
Object v = runtimeService.getVariable(executionId, "status");
Defensive patterns

Strategy: type-guard

Validate before calling

boolean safe = org.activiti.engine.impl.context.Context.getCommandContext() != null;

Type guard

boolean variableAccessAllowed() {
    return org.activiti.engine.impl.context.Context.getCommandContext() != null;
}

Try / catch

try {
    Object v = execution.getVariable("k");
} catch (ActivitiException e) {
    if (e.getMessage().contains("lazy loading outside command context")) {
        v = runtimeService.getVariable(executionId, "k");
    }
}

Prevention

When it happens

Trigger: Any variable access (getVariable(s), getVariableNames, setVariable, hasVariable) on an ExecutionEntity/TaskEntity when Context.getCommandContext() is null — outside a command: application threads, async jobs started manually, cached detached entities, code between commands.

Common situations: Reading process/task variables from a Spring service method invoked outside the engine's command chain with a detached entity; using entities in CompletableFuture/executor threads; custom event listeners invoked without the command interceptor; tests instantiating entities directly.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/264ca3ac1b69f7e4. Report an issue: GitHub.