flowable/flowable-engine · error · ActivitiIllegalArgumentException

Could not resolve loopCardinality expression

Error message

Could not resolve loopCardinality expression '${expressionText}': not a number nor number String

What it means

Thrown by MultiInstanceActivityBehavior.resolveLoopCardinality when the loopCardinality expression evaluates to an object that is neither a java.lang.Number nor a String parseable as an integer. The engine requires an int to know how many instances to spawn, so any other type is rejected.

Solutions

  1. Make the loopCardinality expression evaluate to a numeric type or numeric String, e.g. activiti:loopCardinality="${nrOfItems}" with nrOfInstances as Integer
  2. Convert in a listener/delegate: execution.setVariable("nrOfItems", Integer.parseInt(raw)) before the activity starts
  3. If the value is a countable object, switch to activiti:collection and let the engine derive the count from its size

Example fix

// before
execution.setVariable("nrOfItems", items.size() > 0); // Boolean
// after
execution.setVariable("nrOfItems", items.size()); // Integer
Defensive patterns

Strategy: type-guard

Validate before calling

Object v = execution.getVariable("nrOfItems");
if (v == null || (!(v instanceof Number) && !(v instanceof String && ((String) v).matches("\\d+")))) {
    throw new IllegalStateException("nrOfItems must be a number or numeric String");
}

Type guard

public static boolean isCardinalityValue(Object v) {
    return v instanceof Number || (v instanceof String && ((String) v).trim().matches("\\d+"));
}

Try / catch

try {
    runtimeService.signal(executionId);
} catch (org.activiti.engine.ActivitiIllegalArgumentException e) {
    if (e.getMessage().contains("not a number nor number String")) {
        // coerce the variable to Integer and restart/retry the activity
    }
}

Prevention

When it happens

Trigger: activiti:loopCardinality="${someVar}" where someVar is a Boolean, Date, Map, or arbitrary bean; the expression itself is fine syntactically but returns a non-numeric, non-String value.

Common situations: Pointing loopCardinality at a String with whitespace or non-numeric characters (Integer.valueOf throws NumberFormatException, wrapped or surfaced here for non-String types); a UEL expression returning e.g. a Long wrapper is fine, but a custom type or null result is not; copy-pasting a completion condition (boolean) into loopCardinality.

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/415bd8433438cee7. Report an issue: GitHub.

Appendix: source

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

    protected boolean usesCollection() {
        return collectionExpression != null
                || collectionVariable != null;
    }

    protected boolean isExtraScopeNeeded() {
        // special care is needed when the behavior is an embedded subprocess (not very clean, but it works)
        return innerActivityBehavior instanceof org.activiti.engine.impl.bpmn.behavior.SubProcessActivityBehavior;
    }

    protected int resolveLoopCardinality(ActivityExecution execution) {
        // Using Number since expr can evaluate to eg. Long (which is also the default for Juel)
        Object value = loopCardinalityExpression.getValue(execution);
        if (value instanceof Number) {
            return ((Number) value).intValue();
        } else if (value instanceof String) {
            return Integer.valueOf((String) value);
        } else {
            throw new ActivitiIllegalArgumentException("Could not resolve loopCardinality expression '"
                    + loopCardinalityExpression.getExpressionText() + "': not a number nor number String");
        }
    }

    protected boolean completionConditionSatisfied(ActivityExecution execution) {
        if (completionConditionExpression != null) {
            Object value = completionConditionExpression.getValue(execution);
            if (!(value instanceof Boolean)) {
                throw new ActivitiIllegalArgumentException("completionCondition '"
                        + completionConditionExpression.getExpressionText()
                        + "' does not evaluate to a boolean value");
            }
            Boolean booleanValue = (Boolean) value;
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Completion condition of multi-instance satisfied: {}", booleanValue);
            }
            return booleanValue;
        }

View on GitHub (pinned to d6d39ce1c6)