flowable/flowable-engine · error · ActivitiException

condition expression returns null

Error message

condition expression returns null (sequenceFlowId: ${sequenceFlowId})

What it means

UelExpressionCondition evaluates a sequence-flow condition expression for gateway/flow routing. Flowable requires a Boolean answer to decide routing; if the expression evaluates to null it throws ActivitiException because the condition cannot be resolved.

Solutions

  1. Fix the condition expression to always return a boolean, e.g. ${approved == true} or wrap in Boolean()
  2. Ensure the bean method or variable referenced returns a Boolean, never null
  3. Set the referenced variable before the gateway executes

Example fix

// before
<conditionExpression xsi:type="tFormalExpression">${myBean.check()}</conditionExpression>
// after (bean returns boolean)
<conditionExpression xsi:type="tFormalExpression">${myBean.check() == true}</conditionExpression>
Defensive patterns

Strategy: validation

Validate before calling

Expression e = Context.getProcessEngineConfiguration().getExpressionManager()
        .createExpression(cond);
Object r = e.getValue(execution);
if (r == null || !(r instanceof Boolean)) {
    throw new IllegalArgumentException("Condition must evaluate to Boolean: " + cond);
}

Type guard

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

Try / catch

try {
    flowTaken = condition.evaluate(execution, flowId);
} catch (ActivitiException e) {
    if (e.getMessage() != null && e.getMessage().contains("returns null")) {
        // ensure referenced variable/bean returns a boolean
    }
}

Prevention

When it happens

Trigger: A sequence flow's conditionExpression evaluates to null — e.g. the expression references a missing/empty variable (${approved == null} not used, instead the whole expression returns null) or a method returning void/null.

Common situations: Exclusive/inclusive gateway conditions calling beans whose methods return null; expressions typed against variables that are not set; copy-pasted conditions that aren't boolean.

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

Appendix: source

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

    public UelExpressionCondition(String conditionExpression) {
        this.initialConditionExpression = conditionExpression;
    }

    @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();
                }
            }

View on GitHub (pinned to d6d39ce1c6)