flowable/flowable-engine · error · FlowableException

condition expression returns null (elementId: " + elementId…

Error message

condition expression returns null (elementId: " + elementId + ") for " + execution

What it means

Thrown by UelExpressionCondition.evaluate when a condition expression (e.g. a conditional sequence-flow ${...} expression) evaluates to null. The engine requires a definite Boolean verdict to route execution; null gives no routing information, so the engine fails fast instead of guessing a branch.

Solutions

  1. Set the process variable referenced by the condition before execution reaches the conditional flow (runtimeService.setVariable(executionId, ...) or pass it at startProcessInstanceBy...).
  2. Make the condition null-safe, e.g. ${orderStatus != null && orderStatus == 'OK'}.
  3. Give the gateway a default flow so unset conditions route predictably (though the null verdict still throws - fixing the variable is required).
  4. Log/check which variable is missing: parse elementId from the message and inspect the process XML for the conditionExpression on that flow element.

Example fix

// before: expression references missing variable
<conditionExpression xsi:type="tFormalExpression">${order.status == 'APPROVED'}</conditionExpression>
// after: null-safe expression + guaranteed variable
<conditionExpression xsi:type="tFormalExpression">${order != null &amp;&amp; order.status == 'APPROVED'}</conditionExpression>
// and in Java: runtimeService.startProcessInstanceByKey("order", vars) with vars.put("order", order)
Defensive patterns

Strategy: validation

Validate before calling

// before starting/completing tasks that reach conditional flows
if (vars.get("orderStatus") == null) {
    throw new IllegalStateException("orderStatus must be set before gateway");
}

Type guard

Object v = execution.getVariable("orderStatus");
if (v instanceof String && ((String) v).isEmpty()) {
    throw new IllegalArgumentException("orderStatus empty");
}

Try / catch

try {
    taskService.complete(taskId, vars);
} catch (FlowableException e) {
    if (e.getMessage().contains("condition expression returns null")) {
        // set missing variable and retry
    }
}

Prevention

When it happens

Trigger: Calling evaluate(elementId, execution) where expression.getValue(execution) returns null. Typical API path: process runtime reaching an inclusive/exclusive gateway or conditional flow whose conditionExpression resolves to nothing - e.g. expression ${orderStatus == 'OK'} where variable orderStatus is unset (juel resolves to null), or an expression returning null explicitly.

Common situations: Process variables not set before the gateway (deployed process expects a variable the caller never passes); typos in variable names inside EL expressions; Spring/CDN bean lookups in expressions resolving to null; migrations where a variable was renamed; returning null from a delegateBean method used in the condition.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/8f8bde58b629558a. Report an issue: GitHub.

Appendix: source

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

 * {@link Condition} that resolves an UEL expression at runtime.
 * 
 * @author Joram Barrez
 * @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)