{"record":{"id":"e2bed447024423c0","repo":"flowable/flowable-engine","slug":"skip-expression-does-not-resolve-to-a-boolean","errorCode":null,"errorMessage":"Skip expression does not resolve to a boolean: \" + skipExpression.getExpressionText()","messagePattern":"Skip expression does not resolve to a boolean: \" \\+ skipExpression\\.getExpressionText\\(\\)","errorType":"exception","errorClass":"FlowableIllegalArgumentException","httpStatus":null,"severity":"error","filePath":"modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/helper/SkipExpressionUtil.java","lineNumber":89,"sourceCode":"            return ((Boolean) isSkipExpressionEnabled).booleanValue();\n\n        } else {\n            throw new FlowableIllegalArgumentException(\"Skip expression variable does not resolve to a boolean. \" + isSkipExpressionEnabled);\n        }\n    }\n\n    public static boolean shouldSkipFlowElement(String skipExpressionString, String activityId, DelegateExecution execution, CommandContext commandContext) {\n        ExpressionManager expressionManager = CommandContextUtil.getProcessEngineConfiguration(commandContext).getExpressionManager();\n        Expression skipExpression = expressionManager.createExpression(resolveActiveSkipExpression(skipExpressionString, activityId, \n                        execution.getProcessDefinitionId(), commandContext));\n        \n        Object value = skipExpression.getValue(execution);\n\n        if (value instanceof Boolean) {\n            return ((Boolean) value).booleanValue();\n\n        } else {\n            throw new FlowableIllegalArgumentException(\"Skip expression does not resolve to a boolean: \" + skipExpression.getExpressionText());\n        }\n    }\n    \n    protected static boolean isEnableSkipExpression(ObjectNode globalProperties) {\n        if (globalProperties != null) {\n            JsonNode overrideValueNode = globalProperties.get(DynamicBpmnConstants.ENABLE_SKIP_EXPRESSION);\n            if (overrideValueNode != null && !overrideValueNode.isNull() && \"true\".equalsIgnoreCase(overrideValueNode.asString())) {\n                return true;\n            }\n        }\n        return false;\n    }\n    \n    protected static String resolveActiveSkipExpression(String skipExpression, String activityId, String processDefinitionId, CommandContext commandContext) {\n        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);\n        \n        String activeTaskSkipExpression = null;\n        if (processEngineConfiguration.isEnableProcessDefinitionInfoCache()) {","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/helper/SkipExpressionUtil.java#L71-L107","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Make the expression return a boolean: use comparisons or boolean-returning methods, e.g. ${skipFlag == 'yes'} or ${myService.shouldSkip()}.","Convert the backing variable to Boolean before the activity executes.","Wrap a non-boolean result in a boolean expression: ${Boolean.parseBoolean(result)}.","Log/print the expression result in a test (e.g. via a delegate) to see its actual type, then fix the type."],"exampleFix":"// before\n<flowable:skipExpression>${skipFlag}</flowable:skipExpression> <!-- skipFlag is String \"true\" -->\n\n// after\n<flowable:skipExpression>${skipFlag == 'true'}</flowable:skipExpression>","handlingStrategy":"validation","validationCode":"// assert the expression result is boolean before deployment\nObject result = expressionManager.createExpression(skipExpr).getValue(mockExecution);\nif (!(result instanceof Boolean)) {\n    throw new IllegalStateException(\"skipExpression must evaluate to Boolean: \" + skipExpr);\n}","typeGuard":"static boolean isBooleanExpression(String expr, DelegateExecution exec) {\n    return SkipExpressionUtil.resolveActiveSkipExpression(expr, exec.getCurrentActivityId(), exec) == null\n            || Boolean.class.isAssignableFrom(exprResultType(expr));\n}","tryCatchPattern":"try {\n    skip = SkipExpressionUtil.shouldSkipFlowElement(skipExpr, activityId, execution, commandContext);\n} catch (FlowableIllegalArgumentException e) {\n    skip = false; // never skip on bad expression\n}","preventionTips":["Write skip expressions as boolean comparisons, not raw variable references.","Unit-test skip expressions against representative variable types.","Keep variables feeding skip expressions strictly Boolean."],"tags":["flowable","bpmn","skip-expression","expression","type-mismatch"],"backgroundTag":"type-mismatch","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}