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
- Fix the condition expression to always return a boolean, e.g. ${approved == true} or wrap in Boolean()
- Ensure the bean method or variable referenced returns a Boolean, never null
- 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
- Make every sequence-flow condition a boolean expression
- Ensure bean methods used in conditions return Boolean, never null
- Set all variables referenced by gateway conditions before the gateway executes
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
- condition expression returns non-Boolean
- condition script returns null
- condition script returns null: for
- error.property.method.notfound
- resolver must not be null
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)