flowable/flowable-engine · error · FlowableIllegalArgumentException

variables is empty

Error message

variables is empty

What it means

SetLocalVariablesAsyncCmd.execute() throws FlowableIllegalArgumentException when the variables map is empty. An empty map would produce no work, so the engine treats it as invalid input rather than scheduling a pointless async job.

Source

Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/SetLocalVariablesAsyncCmd.java:44

    
    protected String planItemInstanceId;
    protected Map<String, Object> variables;
    
    public SetLocalVariablesAsyncCmd(String planItemInstanceId, Map<String, Object> variables) {
        this.planItemInstanceId = planItemInstanceId;
        this.variables = variables;
    }
    
    @Override
    public Void execute(CommandContext commandContext) {
        if (planItemInstanceId == null) {
            throw new FlowableIllegalArgumentException("planItemInstanceId is null");
        }
        if (variables == null) {
            throw new FlowableIllegalArgumentException("variables is null");
        }
        if (variables.isEmpty()) {
            throw new FlowableIllegalArgumentException("variables is empty");
        }
     
        CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext);
        PlanItemInstanceEntity planItemInstanceEntity = cmmnEngineConfiguration.getPlanItemInstanceEntityManager().findById(planItemInstanceId);
        if (planItemInstanceEntity == null) {
            throw new FlowableObjectNotFoundException("No plan item instance found for id " + planItemInstanceId, PlanItemInstanceEntity.class);
        }
        
        for (String variableName : variables.keySet()) {
            addVariable(true, planItemInstanceEntity.getCaseInstanceId(), planItemInstanceEntity.getId(), variableName, variables.get(variableName), 
                    planItemInstanceEntity.getTenantId(), cmmnEngineConfiguration.getVariableServiceConfiguration().getVariableService());
        }
        
        createSetAsyncVariablesJob(planItemInstanceEntity, cmmnEngineConfiguration);
        
        return null;
    }

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Check variables.isEmpty() at the call site and skip the async call when there is nothing to set.
  2. Only call setLocalVariablesAsync when at least one variable must be changed.
  3. If the intent is 'no-op', restructure the caller so the command is never scheduled.

Example fix

// before
cmmnRuntimeService.setLocalVariablesAsync(planItemId, changedVars); // may be empty
// after
if (!changedVars.isEmpty()) {
    cmmnRuntimeService.setLocalVariablesAsync(planItemId, changedVars);
}
Defensive patterns

Strategy: validation

Validate before calling

if (variables == null || variables.isEmpty()) {
    return; // nothing to set; skip the async command
}

Type guard

boolean isNonEmpty(Map<String, ?> m) { return m != null && !m.isEmpty(); }

Try / catch

try {
    cmmnRuntimeService.setLocalVariablesAsync(planItemId, vars);
} catch (FlowableIllegalArgumentException e) {
    log.error("setLocalVariablesAsync rejected: {}", e.getMessage());
}

Prevention

When it happens

Trigger: Calling CmmnRuntimeService.setLocalVariablesAsync(planItemInstanceId, Collections.emptyMap()) or with a map filtered down to zero entries before the call.

Common situations: Batch code that computes variable deltas and unconditionally calls the async setter even when nothing changed; upstream filtering removing all entries.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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