flowable/flowable-engine · error · FlowableIllegalArgumentException

Skip expression does not resolve to a boolean: " + skipExpre

Error message

Skip expression does not resolve to a boolean: " + skipExpression.getExpressionText()

What it means

SkipExpressionUtil.shouldSkipFlowElement throws this FlowableIllegalArgumentException when a skip expression evaluates to a value that is not a Boolean. The expression itself resolves fine, but its result type is wrong — the engine can only branch on true/false.

Source

Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/helper/SkipExpressionUtil.java:89

            return ((Boolean) isSkipExpressionEnabled).booleanValue();

        } else {
            throw new FlowableIllegalArgumentException("Skip expression variable does not resolve to a boolean. " + isSkipExpressionEnabled);
        }
    }

    public static boolean shouldSkipFlowElement(String skipExpressionString, String activityId, DelegateExecution execution, CommandContext commandContext) {
        ExpressionManager expressionManager = CommandContextUtil.getProcessEngineConfiguration(commandContext).getExpressionManager();
        Expression skipExpression = expressionManager.createExpression(resolveActiveSkipExpression(skipExpressionString, activityId, 
                        execution.getProcessDefinitionId(), commandContext));
        
        Object value = skipExpression.getValue(execution);

        if (value instanceof Boolean) {
            return ((Boolean) value).booleanValue();

        } else {
            throw new FlowableIllegalArgumentException("Skip expression does not resolve to a boolean: " + skipExpression.getExpressionText());
        }
    }
    
    protected static boolean isEnableSkipExpression(ObjectNode globalProperties) {
        if (globalProperties != null) {
            JsonNode overrideValueNode = globalProperties.get(DynamicBpmnConstants.ENABLE_SKIP_EXPRESSION);
            if (overrideValueNode != null && !overrideValueNode.isNull() && "true".equalsIgnoreCase(overrideValueNode.asString())) {
                return true;
            }
        }
        return false;
    }
    
    protected static String resolveActiveSkipExpression(String skipExpression, String activityId, String processDefinitionId, CommandContext commandContext) {
        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);
        
        String activeTaskSkipExpression = null;
        if (processEngineConfiguration.isEnableProcessDefinitionInfoCache()) {

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Make the expression return a boolean: use comparisons or boolean-returning methods, e.g. ${skipFlag == 'yes'} or ${myService.shouldSkip()}.
  2. Convert the backing variable to Boolean before the activity executes.
  3. Wrap a non-boolean result in a boolean expression: ${Boolean.parseBoolean(result)}.
  4. Log/print the expression result in a test (e.g. via a delegate) to see its actual type, then fix the type.

Example fix

// before
<flowable:skipExpression>${skipFlag}</flowable:skipExpression> <!-- skipFlag is String "true" -->

// after
<flowable:skipExpression>${skipFlag == 'true'}</flowable:skipExpression>
Defensive patterns

Strategy: validation

Validate before calling

// assert the expression result is boolean before deployment
Object result = expressionManager.createExpression(skipExpr).getValue(mockExecution);
if (!(result instanceof Boolean)) {
    throw new IllegalStateException("skipExpression must evaluate to Boolean: " + skipExpr);
}

Type guard

static boolean isBooleanExpression(String expr, DelegateExecution exec) {
    return SkipExpressionUtil.resolveActiveSkipExpression(expr, exec.getCurrentActivityId(), exec) == null
            || Boolean.class.isAssignableFrom(exprResultType(expr));
}

Try / catch

try {
    skip = SkipExpressionUtil.shouldSkipFlowElement(skipExpr, activityId, execution, commandContext);
} catch (FlowableIllegalArgumentException e) {
    skip = false; // never skip on bad expression
}

Prevention

When it happens

Trigger: A flow element's flowable:skipExpression (e.g. on a service task or a flow) evaluates via the expression manager to a String ("yes"), Integer, or null object instead of a Boolean, and shouldSkipFlowElement then calls skipExpression.getValue(execution).

Common situations: Expressions like ${skipFlag} where skipFlag was set as a String variable; expressions returning method-call results whose return type is not boolean (e.g. ${myService.getStatus()}); miswritten expressions like ${var == 'x' ? 'yes' : 'no'} returning strings; i18n mappings replacing boolean outputs.

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