flowable/flowable-engine · error · FlowableException

condition expression returns non-Boolean (elementId: " + ele

Error message

condition expression returns non-Boolean (elementId: " + elementId + "): " + result + " (" + result.getClass().getName() + ") for " + execution

What it means

Thrown by UelExpressionCondition.evaluate when a condition expression evaluates to a non-Boolean value. BPMN conditional flows require a strict Boolean result; any other type (String, Integer, etc.) cannot be used to decide the branch, so the engine throws and includes the actual value and its class name.

Source

Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/el/UelExpressionCondition.java:43

 * @author Frederik Heremans
 */
public class UelExpressionCondition implements Condition {

    protected Expression expression;

    public UelExpressionCondition(Expression expression) {
        this.expression = expression;
    }

    @Override
    public boolean evaluate(String elementId, DelegateExecution execution) {
        Object result = expression.getValue(execution);

        if (result == null) {
            throw new FlowableException("condition expression returns null (elementId: " + elementId + ") for " + execution);
        }
        if (!(result instanceof Boolean)) {
            throw new FlowableException("condition expression returns non-Boolean (elementId: " + elementId + "): " + result + " (" + result.getClass().getName() + ") for " + execution);
        }
        return (Boolean) result;
    }

}

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Convert the value to a real Boolean: set the variable as java.lang.Boolean instead of String.
  2. Make the expression return a Boolean, e.g. ${approved == 'true'} or ${Boolean.parseBoolean(approved)} instead of ${approved}.
  3. Read the class name in the message to identify the offending type, then fix the variable set on the execution.
  4. If the value comes from a form/API, normalize it to boolean before passing to startProcessInstance or setVariable.

Example fix

// before
vars.put("approved", "true"); // String
// after
vars.put("approved", Boolean.TRUE);
Defensive patterns

Strategy: type-guard

Validate before calling

Object approved = vars.get("approved");
if (!(approved instanceof Boolean)) {
    throw new IllegalArgumentException("approved must be Boolean, got " + (approved == null ? null : approved.getClass()));
}

Type guard

boolean isBoolVar(Map<String, Object> vars, String name) {
    return vars.get(name) instanceof Boolean;
}

Try / catch

try {
    runtimeService.startProcessInstanceByKey(key, vars);
} catch (FlowableException e) {
    if (e.getMessage().contains("condition expression returns non-Boolean")) {
        // normalize the variable type named in the message and retry
    }
}

Prevention

When it happens

Trigger: evaluate(elementId, execution) where expression.getValue(execution) returns a non-Boolean, e.g. conditionExpression '${approved}' where variable approved is a String "true" instead of boolean, or an expression like ${count} resolving to an Integer, or an EL method returning a non-boolean type.

Common situations: Variables set as Strings from JSON/forms but used directly in conditions; expressions like ${var == 'x'} accidentally written as ${var} with a String value; returning wrong type from a bean method; languages/data sources where booleans serialize as 'true'/'false' strings.

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/2635c79951d59f0d. Report an issue: GitHub.