flowable/flowable-engine · error · ActivitiIllegalArgumentException

Invalid number of instances: must be non-negative integer…

Error message

Invalid number of instances: must be non-negative integer value, but was ${nrOfInstances}

What it means

Thrown by ParallelMultiInstanceBehavior.createInstances after resolving the instance count: if nrOfInstances is negative, the engine cannot spawn a negative number of child executions, so it throws ActivitiIllegalArgumentException. Note the check is < 0; zero is accepted and the activity completes immediately.

Solutions

  1. Clamp the variable before the activity: execution.setVariable("nrOfItems", Math.max(0, computed))
  2. Fix the source expression so it cannot go negative, e.g. ${a > b ? a - b : 0}
  3. If a custom Collection is used, verify its size() implementation returns a correct non-negative value

Example fix

// before
execution.setVariable("nrOfItems", total - completed); // can be negative
// after
execution.setVariable("nrOfItems", Math.max(0, total - completed));
Defensive patterns

Strategy: validation

Validate before calling

int nr = ((Number) execution.getVariable("nrOfItems")).intValue();
if (nr < 0) {
    execution.setVariable("nrOfItems", 0); // or reject the transition
}

Type guard

public static boolean isNonNegativeInt(Object v) {
    return v instanceof Number && ((Number) v).intValue() >= 0;
}

Try / catch

try {
    runtimeService.signal(executionId);
} catch (org.activiti.engine.ActivitiIllegalArgumentException e) {
    if (e.getMessage().startsWith("Invalid number of instances")) {
        // clamp the cardinality variable and retry the activity
    }
}

Prevention

When it happens

Trigger: resolveNrOfInstances derives a negative count: loopCardinality expression evaluates to a negative number, or a collection variable whose Collection implementation reports a negative size (custom/broken Collection), via MultiInstanceActivityBehavior.resolveNrOfInstances.

Common situations: A variable like remaining = total - completed going below zero and being fed directly into loopCardinality; custom Collection implementations with buggy size(); arithmetic in the UEL expression itself (e.g. ${a - b}) yielding a negative result.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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

Appendix: source

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

import org.flowable.engine.impl.delegate.ActivityBehavior;

/**
 * @author Joram Barrez
 */
public class ParallelMultiInstanceBehavior extends MultiInstanceActivityBehavior {

    public ParallelMultiInstanceBehavior(ActivityImpl activity, ActivityBehavior originalActivityBehavior) {
        super(activity, originalActivityBehavior);
    }

    /**
     * Handles the parallel case of spawning the instances. Will create child executions accordingly for every instance needed.
     */
    @Override
    protected void createInstances(ActivityExecution execution) {
        int nrOfInstances = resolveNrOfInstances(execution);
        if (nrOfInstances < 0) {
            throw new ActivitiIllegalArgumentException("Invalid number of instances: must be non-negative integer value"
                    + ", but was " + nrOfInstances);
        }

        setLoopVariable(execution, NUMBER_OF_INSTANCES, nrOfInstances);
        setLoopVariable(execution, NUMBER_OF_COMPLETED_INSTANCES, 0);
        setLoopVariable(execution, NUMBER_OF_ACTIVE_INSTANCES, nrOfInstances);

        List<ActivityExecution> concurrentExecutions = new ArrayList<>();
        for (int loopCounter = 0; loopCounter < nrOfInstances; loopCounter++) {
            ActivityExecution concurrentExecution = execution.createExecution();
            concurrentExecution.setActive(true);
            concurrentExecution.setConcurrent(true);
            concurrentExecution.setScope(false);

            // In case of an embedded subprocess, and extra child execution is required
            // Otherwise, all child executions would end up under the same parent,
            // without any differentiation to which embedded subprocess they belong
            if (isExtraScopeNeeded()) {

View on GitHub (pinned to d6d39ce1c6)