flowable/flowable-engine · error · ActivitiIllegalArgumentException

Variable ${collectionVariable}' is not a Collection

Error message

Variable ${collectionVariable}' is not a Collection

What it means

When a multi-instance activity references a collectionVariable, the variable must be a java.util.Collection. If the variable exists but holds a non-Collection value (String, array, Map, custom object), resolveNrOfInstances throws this ActivitiIllegalArgumentException. Note the message has a stray quote before 'is'.

Source

Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/MultiInstanceActivityBehavior.java:152

    @SuppressWarnings("rawtypes")
    protected int resolveNrOfInstances(ActivityExecution execution) {
        int nrOfInstances = -1;
        if (loopCardinalityExpression != null) {
            nrOfInstances = resolveLoopCardinality(execution);
        } else if (collectionExpression != null) {
            Object obj = collectionExpression.getValue(execution);
            if (!(obj instanceof Collection)) {
                throw new ActivitiIllegalArgumentException(collectionExpression.getExpressionText() + "' didn't resolve to a Collection");
            }
            nrOfInstances = ((Collection) obj).size();
        } else if (collectionVariable != null) {
            Object obj = execution.getVariable(collectionVariable);
            if (obj == null) {
                throw new ActivitiIllegalArgumentException("Variable " + collectionVariable + " was not found");
            }
            if (!(obj instanceof Collection)) {
                throw new ActivitiIllegalArgumentException("Variable " + collectionVariable + "' is not a Collection");
            }
            nrOfInstances = ((Collection) obj).size();
        } else {
            throw new ActivitiIllegalArgumentException("Couldn't resolve collection expression nor variable reference");
        }
        return nrOfInstances;
    }

    @SuppressWarnings("rawtypes")
    protected void executeOriginalBehavior(ActivityExecution execution, int loopCounter) {
        if (usesCollection() && collectionElementVariable != null) {
            Collection collection = null;
            if (collectionExpression != null) {
                collection = (Collection) collectionExpression.getValue(execution);
            } else if (collectionVariable != null) {
                collection = (Collection) execution.getVariable(collectionVariable);
            }

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Store a java.util.Collection (e.g. ArrayList) in the variable instead of a String/array/map.
  2. Convert in a delegate or listener: Arrays.asList(array) or split a delimited string into a List.
  3. If it must stay a Map, iterate its keySet()/values() and store that Collection in a separate variable.
  4. Audit other writers of the variable (listeners, scripts, REST API set-variable calls) for wrong types.

Example fix

// before
Map<String, String> userMap = fetchUsers();
execution.setVariable("users", userMap);

// after
List<String> users = new ArrayList<>(fetchUsers().keySet());
execution.setVariable("users", users);
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate the variable type before the multi-instance activity runs
Object v = execution.getVariable("users");
if (v != null && !(v instanceof java.util.Collection)) {
    throw new IllegalStateException("Variable 'users' must be a java.util.Collection, got " + v.getClass().getName());
}

Type guard

java.util.Collection<?> asCollection(Object o) {
    if (o instanceof java.util.Collection) return (java.util.Collection<?>) o;
    if (o instanceof Object[]) return java.util.Arrays.asList((Object[]) o);
    if (o instanceof String) return java.util.Arrays.asList(((String) o).split(","));
    if (o instanceof java.util.Map) return ((java.util.Map<?, ?>) o).keySet();
    return null;
}

Try / catch

try {
    runtimeService.startProcessInstanceByKey(key, vars);
} catch (org.activiti.engine.ActivitiIllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().contains("' is not a Collection")) {
        // convert the variable to a java.util.Collection and retry the process instance
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: flowable:collection="myVar" where execution.getVariable("myVar") returns a non-null, non-Collection object when the multi-instance activity is entered.

Common situations: Storing a comma-separated String where a List is expected; serializing JSON into a String variable; putting an array (Object[]) into the variable; a Map is used (Map is not a Collection); a REST/HTTP response body stored as String; type changed between process versions.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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