flowable/flowable-engine · error · FlowableException

Expression condition ${condition} did not evaluate to a bool

Error message

Expression condition ${condition} did not evaluate to a boolean value with ${variableContainer}

What it means

ExpressionUtil.evaluateBooleanExpression evaluates a condition expression and accepts only Boolean results or the strings 'true'/'false' (case-insensitive). Any other result type triggers this FlowableException, because CMMN rules (condition/activation/auto-complete) demand a definitive boolean. It protects against silently treating non-boolean results as false.

Source

Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/util/ExpressionUtil.java:48

import org.flowable.common.engine.api.FlowableIllegalArgumentException;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.api.variable.VariableContainer;
import org.flowable.common.engine.impl.interceptor.CommandContext;

/**
 * @author Joram Barrez
 * @author Micha Kiener
 */
public class ExpressionUtil {

    public static boolean evaluateBooleanExpression(CommandContext commandContext, VariableContainer variableContainer, String condition) {
        Object evaluationResult = evaluateExpression(commandContext, variableContainer, condition);
        if (evaluationResult instanceof Boolean) {
            return (boolean) evaluationResult;
        } else if (evaluationResult instanceof String) {
            return "true".equals(((String) evaluationResult).toLowerCase());
        } else {
            throw new FlowableException("Expression condition " + condition + " did not evaluate to a boolean value with " + variableContainer);
        }
    }

    public static Object evaluateExpression(CommandContext commandContext, VariableContainer variableContainer, String expression) {
        Expression exp = CommandContextUtil.getExpressionManager(commandContext).createExpression(expression);
        return exp.getValue(variableContainer);
    }

    public static boolean isRequiredPlanItemInstance(CommandContext commandContext, PlanItemInstanceEntity planItemInstanceEntity) {
        PlanItemControl planItemControl = planItemInstanceEntity.getPlanItem().getItemControl();
        if (planItemControl != null && planItemControl.getRequiredRule() != null) {

            boolean isRequired = true; // Having a required rule means required by default, unless the condition says otherwise
            String requiredCondition = planItemControl.getRequiredRule().getCondition();
            if (StringUtils.isNotEmpty(requiredCondition)) {
                isRequired = evaluateBooleanExpression(commandContext, planItemInstanceEntity, requiredCondition);
            }
            return isRequired;

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Rewrite the condition to yield a boolean comparison, e.g. ${amount > 0} instead of ${amount}
  2. If the expression returns a String, ensure it is exactly 'true' or 'false' (case-insensitive)
  3. Check for missing/null variables in the variableContainer that make the expression return null; initialize defaults
  4. Unit-test the expression against representative variable values before deploying the case model

Example fix

// before
condition="${orderStatus}"
// after
condition="${orderStatus == 'APPROVED'}"
Defensive patterns

Strategy: type-guard

Validate before calling

Object result = evaluateExpression(cmdCtx, container, condition);
if (!(result instanceof Boolean) && !(result instanceof String s && ("true".equalsIgnoreCase(s) || "false".equalsIgnoreCase(s)))) {
    throw new IllegalStateException("Condition must yield boolean, got: " + result);
}

Type guard

boolean isBooleanExpressionResult(Object v) { return v instanceof Boolean || (v instanceof String s && ("true".equalsIgnoreCase(s) || "false".equalsIgnoreCase(s))); }

Try / catch

try { return ExpressionUtil.evaluateBooleanExpression(cmdCtx, container, condition); } catch (FlowableException e) { if (e.getMessage().contains("did not evaluate to a boolean value")) { log.error("Fix condition {} to return boolean", condition); throw new IllegalArgumentException("Non-boolean condition: " + condition, e); } throw e; }

Prevention

When it happens

Trigger: A plan item condition, ifPart, autoComplete condition or repetition condition expression returns e.g. a number, a collection, a date, or null — e.g. ${myVar} where myVar is an Integer, or a script fragment returning an object — during evaluation by isRequiredPlanItemInstance or evaluateAutoComplete.

Common situations: Expressions copied from scripts returning objects; conditions referencing unset variables evaluating to null; users writing conditions like ${amount} instead of ${amount > 0}; language packs where expression engines return Strings other than true/false.

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/391a335594665d5c. Report an issue: GitHub.