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
- Change the completion condition so it evaluates to a boolean, e.g. activiti:completionCondition="${nrOfCompletedInstances/nrOfInstances >= 0.6}"
- Wrap non-boolean logic with Boolean.parseBoolean() in a listener/delegate and expose a real Boolean variable
- 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
- Write completion conditions as boolean expressions (${nrOfCompletedInstances == nrOfInstances}) rather than variable lookups
- If a variable is used, ensure a delegate stores a real java.lang.Boolean
- Avoid Boolean.parseBoolean strings crossing into process variables meant for conditions
- Review BPMN models during migration: engines with truthiness coercion will surface this in Flowable 5
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
- Could not resolve loopCardinality expression
- ' didn't resolve to a Collection
- completionCondition ' ' does not evaluate to a boolean value
- Could not execute inner activity behavior of multi instance…
- Could not resolve loopCardinality expression
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)