flowable/flowable-engine · error · FlowableIllegalArgumentException

Could not resolve loopCardinality expression

Error message

Could not resolve loopCardinality expression '${loopCardinalityExpression.getExpressionText()}': not a number nor number String

What it means

Thrown when parsing the loopCardinality of a multi-instance activity: the loopCardinalityExpression evaluated to a value that is neither a Number nor a String parsable as an integer, so resolveNumberOfInstances cannot compute the instance count. The message includes the expression text so you can identify the failing condition.

Solutions

  1. Make the expression evaluate to a Number or an integer String, e.g. loopCardinality=3 or ${nrOfItems} with nrOfItems being an Integer.
  2. Initialize the referenced variable with an Integer value before the activity executes.
  3. Strip formatting from numeric strings ('3 ' or '3.0' will not parse) and store plain integers.
  4. Change a boolean/wrong-typed variable to the intended count variable.
  5. Validate the cardinality expression in a unit test before deploying.

Example fix

// before
vars.put("nrOfApprovers", "three");
// after
vars.put("nrOfApprovers", 3);
Defensive patterns

Strategy: validation

Validate before calling

Object c = execution.getVariable("nrOfApprovers");
boolean ok = (c instanceof Number) || (c instanceof String && c.toString().trim().matches("\\d+"));
if (!ok) throw new IllegalStateException("loopCardinality value must be a number or numeric string, got: " + c);

Type guard

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

Try / catch

try {
    return task.execute(execution);
} catch (FlowableIllegalArgumentException ex) {
    if (ex.getMessage().contains("loopCardinality")) {
        logger.error("Invalid loopCardinality expression result: {}", ex.getMessage());
    }
    throw ex;
}

Prevention

When it happens

Trigger: <multiInstanceLoopCharacteristics><loopCardinality>${expr}</loopCardinality> where expr evaluates to null, a Boolean, a non-numeric String like 'three', or an object; also Integer.valueOf failing on strings with spaces or decimals.

Common situations: loopCardinality pointing at an unset (null) variable; variable holding '3.5' or '3 ' (non-parseable string); expression returning a Long/BigInteger path is fine but a custom object or boolean is returned; copy-paste of an expression referencing the wrong variable.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/MultiInstanceActivityBehavior.java:573

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

    protected boolean isExtraScopeNeeded(FlowNode flowNode) {
        return flowNode.getSubProcess() != null;
    }

    protected int resolveLoopCardinality(DelegateExecution 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 FlowableIllegalArgumentException("Could not resolve loopCardinality expression '" + loopCardinalityExpression.getExpressionText() + "': not a number nor number String");
        }
    }

    protected void setLoopVariable(DelegateExecution execution, String variableName, Object value) {
        execution.setVariableLocal(variableName, value);
    }

    protected Integer getLocalLoopVariable(DelegateExecution execution, String variableName) {
        Map<String, Object> localVariables = execution.getVariablesLocal();
        if (localVariables.containsKey(variableName)) {
            return (Integer) execution.getVariableLocal(variableName);
            
        } else if (!execution.isMultiInstanceRoot()) {
            DelegateExecution parentExecution = execution.getParent();
            localVariables = parentExecution.getVariablesLocal();
            if (localVariables.containsKey(variableName)) {
                return (Integer) parentExecution.getVariableLocal(variableName);
                

View on GitHub (pinned to d6d39ce1c6)