flowable/flowable-engine · error · UnsupportedOperationException

UnsupportedOperationException

Error message

UnsupportedOperationException

What it means

VariableContainerWrapper is a simple adapter wrapping a variables map, and its default implementation does not support transient variables — setTransientVariable always throws UnsupportedOperationException. Transient variables (not persisted, valid only for the current transaction/thread) require a VariableContainer that explicitly implements this method.

Solutions

  1. Do not set transient variables on VariableContainerWrapper; use a real DelegateExecution/VariableScope obtained from the engine.
  2. Extend VariableContainerWrapper and override setTransientVariable if your use case needs in-memory transient storage.
  3. In tests, use a mock of DelegateExecution/VariableContainer that implements setTransientVariable (e.g. store in a local map).

Example fix

// before
VariableContainer c = new VariableContainerWrapper(map);
c.setTransientVariable("loopCounter2", 1); // throws

// after
VariableContainer c = new VariableContainerWrapper(map) {
    @Override
    public void setTransientVariable(String name, Object value) {
        transientMap.put(name, value);
    }
};
Defensive patterns

Strategy: type-guard

Validate before calling

if (container instanceof VariableContainerWrapper) {
    throw new IllegalStateException("This code path sets transient variables; use a DelegateExecution-backed container");
}

Type guard

boolean supportsTransient(VariableContainer c) {
    try { c.setTransientVariable("__probe__", null); return true; }
    catch (UnsupportedOperationException e) { return false; }
}

Try / catch

try {
    container.setTransientVariable(name, value);
} catch (UnsupportedOperationException e) {
    container.setVariable(name, value); // fallback to a regular variable if transient semantics are not required
}

Prevention

When it happens

Trigger: Calling setTransientVariable(name, value) on a VariableContainerWrapper instance; passing a VariableContainerWrapper as the execution context to code (e.g. expression functions, delegates) that sets transient variables.

Common situations: Using VariableContainerWrapper to fake a DelegateExecution in unit tests or custom expression evaluation while the evaluated logic (e.g. a Flowable expression function or command) writes transient variables; upgrading Flowable where new internal code paths call setTransientVariable on containers that previously only needed setVariable.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/el/VariableContainerWrapper.java:55

    @Override
    public boolean hasVariable(String variableName) {
        return variables.containsKey(variableName);
    }

    @Override
    public Object getVariable(String variableName) {
        return variables.get(variableName);
    }

    @Override
    public void setVariable(String variableName, Object variableValue) {
        variables.put(variableName, variableValue);
    }
    
    @Override
    public void setTransientVariable(String variableName, Object variableValue) {
        throw new UnsupportedOperationException();
    }

    public String getInstanceId() {
        return instanceId;
    }

    public void setInstanceId(String instanceId) {
        this.instanceId = instanceId;
    }

    public String getScopeType() {
        return scopeType;
    }

    public void setScopeType(String scopeType) {
        this.scopeType = scopeType;
    }

View on GitHub (pinned to d6d39ce1c6)