flowable/flowable-engine · error · FlowableIllegalArgumentException

ExecuteDecisionContext is null

Error message

ExecuteDecisionContext is null

What it means

PersistHistoricDecisionExecutionCmd persists a historic decision execution record using the ExecuteDecisionContext supplied to its constructor. If that context is null the command throws FlowableIllegalArgumentException, because it cannot derive the decision execution data to store. It is invoked from the ExecuteDecisionActivityBehavior during DMN rule execution.

Solutions

  1. Never construct PersistHistoricDecisionExecutionCmd manually; let the ExecuteDecisionActivityBehavior supply a properly built context
  2. If writing custom behavior, build an ExecuteDecisionContext with non-null decisionExecution data before persisting history
  3. Check for overrides or forks of ExecuteDecisionActivityBehavior that skip context creation

Example fix

// before
new PersistHistoricDecisionExecutionCmd(null).execute(commandContext);
// after
ExecuteDecisionContext ctx = new ExecuteDecisionContext(decisionExecution);
new PersistHistoricDecisionExecutionCmd(ctx).execute(commandContext);
Defensive patterns

Strategy: validation

Validate before calling

if (ctx == null || ctx.getDecisionExecution() == null) {
    throw new IllegalStateException("ExecuteDecisionContext must be built before persisting history");
}

Type guard

boolean hasContext(ExecuteDecisionContext c) { return c != null && c.getDecisionExecution() != null; }

Try / catch

try {
    behavior.execute(execution);
} catch (FlowableIllegalArgumentException e) {
    if (e.getMessage().contains("ExecuteDecisionContext is null")) {
        log.error("Decision context missing during history persist", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Executing a DMN decision through the engine (e.g. DmnEngine.executeDecide or an ExecuteDecisionActivityBehavior flow) where the internally constructed ExecuteDecisionContext is null — typically after a failed or misconfigured decision execution setup.

Common situations: Custom code building PersistHistoricDecisionExecutionCmd manually with a null context; engine bugs or subclass overrides that leave the context unset when history is enabled.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/ceba7cbf5e20c8b0. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/cmd/PersistHistoricDecisionExecutionCmd.java:44

import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;

/**
 * @author Yvo Swillens
 */
public class PersistHistoricDecisionExecutionCmd implements Command<Void> {

    protected ExecuteDecisionContext executeDecisionContext;

    public PersistHistoricDecisionExecutionCmd(ExecuteDecisionContext executeDecisionContext) {
        this.executeDecisionContext = executeDecisionContext;
    }

    @Override
    public Void execute(CommandContext commandContext) {

        if (executeDecisionContext == null) {
            throw new FlowableIllegalArgumentException("ExecuteDecisionContext is null");
        }

        DmnEngineConfiguration engineConfiguration = CommandContextUtil.getDmnEngineConfiguration();

        if (engineConfiguration.isHistoryEnabled()) {
            HistoricDecisionExecutionEntityManager historicDecisionExecutionEntityManager = engineConfiguration.getHistoricDecisionExecutionEntityManager();
            HistoricDecisionExecutionEntity decisionExecutionEntity = historicDecisionExecutionEntityManager.create();
            decisionExecutionEntity.setDecisionDefinitionId(executeDecisionContext.getDecisionId());
            decisionExecutionEntity.setDeploymentId(executeDecisionContext.getDeploymentId());
            decisionExecutionEntity.setStartTime(executeDecisionContext.getDecisionExecution().getStartTime());
            decisionExecutionEntity.setEndTime(executeDecisionContext.getDecisionExecution().getEndTime());
            decisionExecutionEntity.setInstanceId(executeDecisionContext.getInstanceId());
            decisionExecutionEntity.setExecutionId(executeDecisionContext.getExecutionId());
            decisionExecutionEntity.setActivityId(executeDecisionContext.getActivityId());
            decisionExecutionEntity.setScopeType(executeDecisionContext.getScopeType());
            decisionExecutionEntity.setTenantId(executeDecisionContext.getTenantId());

            Boolean failed = executeDecisionContext.getDecisionExecution().isFailed();

View on GitHub (pinned to d6d39ce1c6)