flowable/flowable-engine · error · FlowableException
rule cannot be null
Error message
rule cannot be null
What it means
executeRule guards against a null DecisionRule while iterating a decision table's rules (RuleEngineExecutorImpl.java:164-167), throwing FlowableException("rule cannot be null"). It protects the per-rule evaluation (audit entry, input-expression evaluation) from dereferencing a null rule. The exception bubbles up to evaluateDecisionTable's FlowableException catch, so the audit container is marked failed with this message rather than crashing the whole execute call.
Solutions
- Sanitize the decision table before execution: remove null elements from decisionTable.getRules().
- Fix the model source — inspect the deployed .dmn XML or the converter/transformer producing the rules list.
- If building tables programmatically, validate each rule is non-null before adding it.
- Handle the failure gracefully: catch FlowableException from execute() and check the audit container's isFailed()/exceptionMessage.
Example fix
// before
table.getRules().add(null); // later throws "rule cannot be null"
// after
for (DecisionRule rule : rules) {
if (rule != null) {
table.getRules().add(rule);
}
} Defensive patterns
Strategy: validation
Validate before calling
List<DecisionRule> rules = ((DecisionTable) decision.getExpression()).getRules();
if (rules.stream().anyMatch(Objects::isNull)) {
throw new IllegalStateException("decision table contains null rules; fix the model source");
} Try / catch
try {
audit = ruleEngineExecutor.execute(decision, ctx);
} catch (FlowableException fe) {
if ("rule cannot be null".equals(fe.getMessage())) {
log.error("Corrupt DMN model: null rule in table", fe);
}
} Prevention
- Reject null rules when constructing tables programmatically.
- Validate generated .dmn XML against the DMN schema before deployment.
- Audit any custom model transformers/migrations that touch the rules list.
When it happens
Trigger: A DecisionTable whose getRules() list contains a null element — typically from a corrupted/hand-edited .dmn model, a custom model-to-model conversion that inserts null entries, or programmatic table construction adding null rules. Called from executeRule via the rules loop in evaluateDecisionTable (via ruleResult path).
Common situations: Generating DMN XML dynamically where a rule template rendered an empty/null entry, custom model transformers or migration code populating the rules list incorrectly, or reflective calls to executeRule(null, ctx).
Related errors
- decision tenantId is null
- key is null
- keyLike is null
- no execution context available
- resourceName is null
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/18f0faa31c7c3ae9.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/RuleEngineExecutorImpl.java:166
// post rule conclusion actions
if (getHitPolicyBehavior(decisionTable.getHitPolicy()) instanceof ComposeDecisionResultBehavior) {
getHitPolicyBehavior(decisionTable.getHitPolicy()).composeDecisionResults(executionContext);
}
} catch (FlowableException ade) {
LOGGER.error("decision table execution failed", ade);
executionContext.getRuleResults().clear();
executionContext.getAuditContainer().setFailedWithException(ade);
executionContext.getAuditContainer().setExceptionMessage(getExceptionMessage(ade));
}
LOGGER.debug("End table evaluation: {}", decisionTable.getId());
}
protected boolean executeRule(DecisionRule rule, ELExecutionContext executionContext) {
if (rule == null) {
throw new FlowableException("rule cannot be null");
}
LOGGER.debug("Start rule {} evaluation", rule.getRuleNumber());
// add audit entry
executionContext.getAuditContainer().addRuleEntry(rule);
boolean conditionResult = false;
// go through conditions
for (RuleInputClauseContainer conditionContainer : rule.getInputEntries()) {
// resetting value
String inputEntryId = conditionContainer.getInputEntry().getId();
conditionResult = false;
try {
// if condition is empty condition or has dash symbol result is TRUEView on GitHub (pinned to d6d39ce1c6)