flowable/flowable-engine · error · ActivitiException

condition expression returns non-Boolean

Error message

condition expression returns non-Boolean (sequenceFlowId: ${sequenceFlowId}): ${result} (${result.getClass().getName()})

What it means

After evaluation, UelExpressionCondition type-checks the result: a sequence-flow condition must yield a Boolean. Any non-Boolean value (String, Integer, etc.) throws ActivitiException naming the flow id, the value and its class.

Solutions

  1. Rewrite the condition to a boolean expression, e.g. ${status == 'active'} instead of ${status}
  2. Make the invoked bean method return boolean/Boolean
  3. Declare the variable type properly and use comparison operators in the condition

Example fix

// before
<conditionExpression xsi:type="tFormalExpression">${status}</conditionExpression>
// after
<conditionExpression xsi:type="tFormalExpression">${status == 'active'}</conditionExpression>
Defensive patterns

Strategy: validation

Validate before calling

Object r = expression.getValue(execution);
if (!(r instanceof Boolean)) {
    throw new IllegalArgumentException("Condition must be Boolean, got: "
        + (r == null ? "null" : r.getClass().getName()));
}

Type guard

boolean isBooleanCondition(Object r) {
    return r instanceof Boolean;
}

Try / catch

try {
    condition.evaluate(execution, sequenceFlowId);
} catch (ActivitiException e) {
    if (e.getMessage() != null && e.getMessage().contains("non-Boolean")) {
        // rewrite expression with comparison operator
    }
}

Prevention

When it happens

Trigger: A conditionExpression returning a truthy non-boolean, e.g. ${status} where status is "active", or a numeric/string expression used as a gateway condition.

Common situations: Migrating from engines/versions with looser condition typing; using string comparisons that return the string rather than a comparison; calling methods that return Object.

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

Appendix: source

Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/el/UelExpressionCondition.java:59

    @Override
    public boolean evaluate(String sequenceFlowId, DelegateExecution execution) {
        String conditionExpression = null;
        if (Context.getProcessEngineConfiguration().isEnableProcessDefinitionInfoCache()) {
            ObjectNode elementProperties = Context.getBpmnOverrideElementProperties(sequenceFlowId, execution.getProcessDefinitionId());
            conditionExpression = getActiveValue(initialConditionExpression, DynamicBpmnConstants.SEQUENCE_FLOW_CONDITION, elementProperties);
        } else {
            conditionExpression = initialConditionExpression;
        }

        Expression expression = Context.getProcessEngineConfiguration().getExpressionManager().createExpression(conditionExpression);
        Object result = expression.getValue(execution);

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

    protected String getActiveValue(String originalValue, String propertyName, ObjectNode elementProperties) {
        String activeValue = originalValue;
        if (elementProperties != null) {
            JsonNode overrideValueNode = elementProperties.get(propertyName);
            if (overrideValueNode != null) {
                if (overrideValueNode.isNull()) {
                    activeValue = null;
                } else {
                    activeValue = overrideValueNode.asString();
                }
            }
        }
        return activeValue;
    }

View on GitHub (pinned to d6d39ce1c6)