flowable/flowable-engine · error · PvmException
joining scope executions is not allowed
Error message
joining scope executions is not allowed
What it means
A PvmException thrown by ExecutionImpl.takeAll when more than one execution is being recycled/joined at an activity and at least one of them is a scope execution. Scope executions (e.g. those owning process/scope variables or sub-scopes) may not be joined together. This guards the execution-tree scope invariants during concurrent join (e.g. parallel gateway merge).
Solutions
- Redesign the custom join behavior to only recycle non-scope concurrent (leaf) executions
- Move the join out of the sub-process scope so participating executions are not scope roots
- Use the engine's built-in parallel/inclusive gateway instead of custom takeAll logic
- Check whether the joined executions became scopes due to async boundaries/event scopes and adjust the model
Example fix
// before
List<ActivityExecution> joined = executions.stream()
.filter(e -> ((ExecutionImpl) e).isScope() || true) // recycles scopes too
.collect(toList());
takeAll(joined, null);
// after
List<ActivityExecution> leaves = executions.stream()
.filter(e -> !((ExecutionImpl) e).isScope())
.collect(toList());
takeAll(leaves, null); Defensive patterns
Strategy: validation
Validate before calling
// before takeAll, verify no recyclable execution is a scope
boolean scopeJoin = recyclable.stream().anyMatch(e -> ((ExecutionImpl) e).isScope());
if (scopeJoin) throw new ActivitiException("cannot join scope executions"); Type guard
boolean isLeaf(PvmExecution e) { return e instanceof ExecutionImpl && !((ExecutionImpl) e).isScope(); } Try / catch
try {
customJoin.executions(recyclable);
} catch (PvmException e) {
if (e.getMessage().contains("joining scope executions")) { /* redesign join */ }
} Prevention
- Only recycle non-scope concurrent executions in custom joins
- Prefer standard parallel gateway merge semantics
- Review scope roots when forking inside embedded sub-processes
When it happens
Trigger: takeAll() called with multiple recyclable executions where isScope() is true for one of them — typically from custom concurrency behaviors attempting to join executions that own scopes (embedded sub-process scope roots) instead of leaf concurrent executions.
Common situations: Custom join/merge behaviors incorrectly recycling scope-rooted executions; modeling constructs that fork inside an embedded sub-process and join at scope level; engine-version migrations where scope semantics changed.
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
- already taking a transition
- didn't expect active execution in
- joining scope executions is not allowed
- variable ' ' already exists. Use setVariableLocal if you…
- Can only enable a plan item instance which is in state…
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/296e0f183adb01ad.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/pvm/runtime/ExecutionImpl.java:641
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("inactive concurrent executions in '{}': {}", activity, inactiveConcurrentExecutionsInActivity);
LOGGER.debug("other concurrent executions: {}", otherConcurrentExecutions);
}
return inactiveConcurrentExecutionsInActivity;
}
@Override
@SuppressWarnings("unchecked")
public void takeAll(List<PvmTransition> transitions, List<ActivityExecution> recyclableExecutions) {
transitions = new ArrayList<>(transitions);
recyclableExecutions = (recyclableExecutions != null ? new ArrayList<>(recyclableExecutions) : new ArrayList<>());
if (recyclableExecutions.size() > 1) {
for (ActivityExecution recyclableExecution : recyclableExecutions) {
if (((ExecutionImpl) recyclableExecution).isScope()) {
throw new PvmException("joining scope executions is not allowed");
}
}
}
ExecutionImpl concurrentRoot = ((isConcurrent && !isScope) ? getParent() : this);
List<ExecutionImpl> concurrentActiveExecutions = new ArrayList<>();
for (ExecutionImpl execution : concurrentRoot.getExecutions()) {
if (execution.isActive()) {
concurrentActiveExecutions.add(execution);
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("transitions to take concurrent: {}", transitions);
LOGGER.debug("active concurrent executions: {}", concurrentActiveExecutions);
}
if ((transitions.size() == 1)View on GitHub (pinned to d6d39ce1c6)