flowable/flowable-engine · error · org.activiti.engine.ActivitiException

condition script returns non-Boolean

Error message

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

What it means

ScriptCondition.evaluate requires the condition script result to be a Boolean. When the script returns a non-Boolean value (String, Number, etc.) it throws ActivitiException including the result and its class name, because sequence-flow conditions must resolve to true/false.

Solutions

  1. Make the script return an actual boolean (use comparison operators, not string/number results).
  2. Convert explicitly: e.g. Boolean.parseBoolean(...) inside the script before returning.
  3. Check which script engine/language is configured — different engines box results differently; normalize in the script.
  4. For JavaScript, write 'return value === true;' instead of returning a truthy expression.

Example fix

// before (JS condition)
return approved; // approved is 1 or "yes"
// after
return approved === true || approved === "yes";
Defensive patterns

Strategy: validation

Validate before calling

Object r = scriptingEngines.evaluate(expr, language, execution);
if (!(r instanceof Boolean)) {
    throw new IllegalStateException("condition must return Boolean, got " + (r == null ? "null" : r.getClass()));
}

Type guard

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

Try / catch

try {
    return ScriptCondition.evaluate(...);
} catch (ActivitiException e) {
    if (e.getMessage().startsWith("condition script returns non-Boolean")) {
        throw new InvalidConditionException(e.getMessage());
    }
    throw e;
}

Prevention

When it happens

Trigger: A condition script whose evaluation result is not a Boolean instance — e.g. returning a String "true", an Integer flag, or a truthy object from the scripting engine.

Common situations: Scripts returning string comparisons reversed ('true' vs true), JavaScript returning numbers/strings where Java expects Boolean, Groovy returning Integer result of a comparison wrapper, cross-version behavior where a different script engine returns boxed types differently.

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

Appendix: source

Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/scripting/ScriptCondition.java:58

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

        ScriptingEngines scriptingEngines = Context
                .getProcessEngineConfiguration()
                .getScriptingEngines();

        Object result = scriptingEngines.evaluate(conditionExpression, language, execution);
        if (result == null) {
            throw new ActivitiException("condition script returns null: " + expression);
        }
        if (!(result instanceof Boolean)) {
            throw new ActivitiException("condition script returns non-Boolean: " + 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)