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

  1. Use executeDecisionService/executeDecisionServiceWithSingleResult (or the audit-trail variant) so the execution runs as a decision service
  2. For a plain Decision, use the decision-result APIs (decisionResult/executeWithAuditTrail) instead
  3. 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

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


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)