flowable/flowable-engine · error · ActivitiIllegalArgumentException

completionCondition ' ' does not evaluate to a boolean value

Error message

completionCondition '${expressionText}' does not evaluate to a boolean value

What it means

Thrown by MultiInstanceActivityBehavior.completionConditionSatisfied when a multi-instance activity defines a completion condition whose expression evaluates to something other than a java.lang.Boolean. The engine only branches on a strict Boolean, so any other result type makes the condition indeterminable and it throws ActivitiIllegalArgumentException.

Solutions

  1. Change the completion condition so it evaluates to a boolean, e.g. activiti:completionCondition="${nrOfCompletedInstances/nrOfInstances >= 0.6}"
  2. Wrap non-boolean logic with Boolean.parseBoolean() in a listener/delegate and expose a real Boolean variable
  3. Remove the completionCondition attribute if the intent was to complete only when all instances finish

Example fix

// before
<multiInstanceLoopCharacteristics ... activiti:completionCondition="${completeFlag}"/> <!-- completeFlag is String "true" -->
// after
execution.setVariable("completeFlag", Boolean.parseBoolean(rawFlag)); // real Boolean
Defensive patterns

Strategy: validation

Validate before calling

Object v = execution.getVariable("completeFlag");
if (!(v instanceof Boolean)) {
    throw new IllegalStateException("completion condition variable must be a Boolean");
}

Type guard

public static boolean isBoolean(Object v) {
    return v instanceof Boolean;
}

Try / catch

try {
    runtimeService.signal(executionId);
} catch (org.activiti.engine.ActivitiIllegalArgumentException e) {
    if (e.getMessage().contains("does not evaluate to a boolean value")) {
        // fix the completionCondition expression or variable type
    }
}

Prevention

When it happens

Trigger: activiti:completionCondition="${x}" where x is a String "true", an Integer 0/1, or any non-Boolean object; UEL does not coerce truthiness, so ${nrCompleted >= nrInstances} is fine but a variable holding "true" as String is not.

Common situations: Returning a String from a delegate instead of Boolean; using a script/expression that yields null when variables are missing; porting models from engines/languages with looser truthiness rules (JS-style) into Flowable 5.

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/8b98e437a0882e46. Report an issue: GitHub.

Appendix: source

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

    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;
        }
        return false;
    }

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

    protected Integer getLoopVariable(ActivityExecution execution, String variableName) {
        Object value = execution.getVariableLocal(variableName);

View on GitHub (pinned to d6d39ce1c6)