flowable/flowable-engine · error · FlowableException
Main execution was a not a decision service
Error message
Main execution was a not a decision service
What it means
composeDecisionServiceResult is only valid when the main execution was a decision service. If the ExecuteDecisionContext was not executed as a decision service, Flowable throws this FlowableException because composing a decision-service result is impossible.
Solutions
- Use executeDecisionService/executeDecisionServiceWithSingleResult (or the audit-trail variant) so the execution runs as a decision service
- For a plain Decision, use the decision-result APIs (decisionResult/executeWithAuditTrail) instead
- If extending the engine, check executeDecisionContext.getDmnElement() instanceof DecisionService before composing
Example fix
// before
Object result = decisionService.executeWithAuditTrail(ctx); // ctx wraps a plain Decision
// after
if (ctx.getDmnElement() instanceof DecisionService) {
result = decisionService.executeDecisionServiceWithAuditTrail(ctx);
} else {
result = decisionService.executeWithAuditTrail(ctx);
} Defensive patterns
Strategy: type-guard
Validate before calling
if (!(ctx.getDmnElement() instanceof DecisionService)) {
throw new IllegalArgumentException("Context must wrap a DecisionService for this API");
} Type guard
boolean isDecisionService(ExecuteDecisionContext ctx) {
return ctx != null && ctx.getDmnElement() instanceof DecisionService;
} Try / catch
try {
result = decisionService.executeDecisionServiceWithAuditTrail(ctx);
} catch (FlowableException e) {
if (e.getMessage().contains("Main execution was a not a decision service")) { /* use plain-decision APIs instead */ }
throw e;
} Prevention
- Match the API to the element type: decision-service APIs for DecisionService, decision APIs for Decision
- Check getDmnElement() before choosing a composition path in subclasses
- Avoid calling protected compose* helpers outside their intended element type
When it happens
Trigger: Calling composeDecisionServiceResult (directly or via decisionResult/executeDecisionServiceWithAuditTrail) with a context that executed a plain Decision instead of a DecisionService, or a mis-built internal context.
Common situations: Mixing single-decision evaluation APIs with decision-service result composition; calling internal protected methods in a subclass against the wrong element type; version mismatches where an API changed semantics.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Execution was not a decision or decision service
- Could not find decision service with id
- decisionKey is null
- no decision service with id
- a suspended
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/4bc72c030ce440f7.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/DmnDecisionServiceImpl.java:234
if (executeDecisionContext.getDmnElement() instanceof DecisionService decisionService) {
Map<String, List<Map<String, Object>>> decisionServiceResult = new HashMap<>();
DecisionServiceExecutionAuditContainer decisionServiceExecutionAuditContainer = (DecisionServiceExecutionAuditContainer) executeDecisionContext.getDecisionExecution();
boolean multipleResults = decisionService.getOutputDecisions().size() > 1;
for (DmnElementReference elementReference : decisionService.getOutputDecisions()) {
DecisionExecutionAuditContainer childDecisionExecution = decisionServiceExecutionAuditContainer
.getChildDecisionExecution(elementReference.getParsedId());
if (childDecisionExecution.getDecisionResult() != null && !childDecisionExecution.getDecisionResult().isEmpty()) {
decisionServiceResult.put(elementReference.getParsedId(), childDecisionExecution.getDecisionResult());
}
multipleResults = multipleResults || childDecisionExecution.isMultipleResults();
}
decisionServiceExecutionAuditContainer.setDecisionServiceResult(decisionServiceResult);
decisionServiceExecutionAuditContainer.setMultipleResults(multipleResults);
return decisionServiceResult;
} else {
throw new FlowableException("Main execution was a not a decision service");
}
}
protected DecisionExecutionAuditContainer finalizeDecisionExecutionAudit(ExecuteDecisionContext executeDecisionContext) {
DecisionExecutionAuditContainer decisionExecution = executeDecisionContext.getDecisionExecution();
decisionExecution.stopAudit(configuration.getClock().getCurrentTime());
if (!executeDecisionContext.isDisableHistory()) {
commandExecutor.execute(new PersistHistoricDecisionExecutionCmd(executeDecisionContext));
}
return decisionExecution;
}
}
View on GitHub (pinned to d6d39ce1c6)