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
- Store a java.util.Collection (e.g. ArrayList) in the variable instead of a String/array/map.
- Convert in a delegate or listener: Arrays.asList(array) or split a delimited string into a List.
- If it must stay a Map, iterate its keySet()/values() and store that Collection in a separate variable.
- 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
- Only store java.util.Collection instances in variables referenced by flowable:collection.
- Remember Map, Object[] and String are NOT Collections even though they feel collection-like.
- Centralize variable creation in one delegate to keep types consistent.
- Check all writers (scripts, REST API, listeners) of the variable enforce the collection type.
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
- ${collectionExpressionText}' didn't resolve to a Collection
- Variable ${collectionVariable} was not found
- completionCondition '${activeCompletionCondition}' does not
- Variable '${obj}':${collectionVariable} is not a Collection
- condition expression returns non-Boolean (elementId: " + ele
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/c780018f57377603.
Report an issue: GitHub.