flowable/flowable-engine · error · PvmException

joining scope executions is not allowed

Error message

joining scope executions is not allowed

What it means

Flowable/Activiti's process model concurrency (parallel gateway / multi-instance) joins multiple incoming executions into one. When more than one incoming execution arrives and any of them is a scope execution (an execution that carries local variables/its own variable scope), the engine cannot merge them and throws this PvmException. This is an internal BPMN modeling constraint enforced at runtime during transition taking.

Solutions

  1. Redesign the BPMN model so parallel flows that converge pass through non-scope executions; end each branch with an end event inside its scope so the scope terminates before joining.
  2. Place a parallel gateway before the subprocess exit so the scope execution ends and a fresh non-scope execution continues to the joining gateway.
  3. If branches carry local variables, move those variables to a parent scope or copy them explicitly before the join.
  4. Upgrade/verify engine version: newer Flowable versions handle some scope joins; check release notes for gateway/scope join fixes.

Example fix

// before: branches out of embedded subprocesses converge directly on one parallel gateway
<parallelGateway id="join"/>
... <sequenceFlow sourceRef="subprocess1" targetRef="join"/>

// after: terminate each scope with an end event, then join
<subprocess id="sub1"><endEvent id="sub1End"/>...</subprocess>
<sequenceFlow sourceRef="sub1" targetRef="join"/>
Defensive patterns

Strategy: validation

Validate before calling

// Model check: ensure no join gateway directly follows an embedded subprocess/callActivity without an intervening end event
boolean joinsScopeExit = bpmnModel.getGateways().stream()
    .anyMatch(g -> g.getIncomingFlows().stream()
        .anyMatch(f -> isScopeCreatingActivity(bpmnModel, f.getSourceRef())));
if (joinsScopeExit) throw new ModelValidationException("parallel gateway joins scope executions");

Prevention

When it happens

Trigger: Calling takeAll with recyclableExecutions.size() > 1 where at least one incoming execution has isScope()==true; typically a parallel/inclusive gateway or merge activity whose incoming flows originate from scope-creating activities (e.g. embedded subprocess call activities or activities with local scoped executions).

Common situations: BPMN models where a parallel gateway joins flows coming out of embedded subprocesses or call activities; using an inclusive gateway to merge scope executions; models migrated from older engine versions that previously tolerated scope joins.

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/fb7fd38ea4069b1e. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/persistence/entity/ExecutionEntity.java:553

            childExecutions.add(childExecution);
            childExecutions.addAll(childExecution.getAllChildExecutions());
        }
        return childExecutions;
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public void takeAll(List<PvmTransition> transitions, List<ActivityExecution> recyclableExecutions) {

        fireActivityCompletedEvent();

        transitions = new ArrayList<>(transitions);
        recyclableExecutions = (recyclableExecutions != null ? new ArrayList<>(recyclableExecutions) : new ArrayList<>());

        if (recyclableExecutions.size() > 1) {
            for (ActivityExecution recyclableExecution : recyclableExecutions) {
                if (((ExecutionEntity) recyclableExecution).isScope()) {
                    throw new PvmException("joining scope executions is not allowed");
                }
            }
        }

        ExecutionEntity concurrentRoot = ((isConcurrent && !isScope) ? getParent() : this);
        List<ExecutionEntity> concurrentActiveExecutions = new ArrayList<>();
        List<ExecutionEntity> concurrentInActiveExecutions = new ArrayList<>();
        for (ExecutionEntity execution : concurrentRoot.getExecutions()) {
            if (execution.isActive()) {
                concurrentActiveExecutions.add(execution);
            } else {
                concurrentInActiveExecutions.add(execution);
            }
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("transitions to take concurrent: {}", transitions);
            LOGGER.debug("active concurrent executions: {}", concurrentActiveExecutions);

View on GitHub (pinned to d6d39ce1c6)