flowable/flowable-engine · error · FlowableException

Call activity '" + executionActivityId + "' loop…

Error message

Call activity '" + executionActivityId + "' loop characteristics differs in new model. It must be mapped explicitly for migration (or all its child activities)

What it means

Thrown when a call activity with running unmapped children exists in both models but exactly one of the models declares multi-instance loop characteristics for it. A change in loop cardinality/structure of an actively running multi-instance call activity cannot be auto-migrated, so an explicit mapping is required.

Solutions

  1. Add an explicit activity mapping for the call activity in the migration document
  2. Keep loop characteristics identical in the target model version
  3. Terminate or complete the running multi-instance children before migrating
  4. Redesign the migration in steps: complete the MI branch, then migrate to the new model

Example fix

// before
// callActivity had no MI in v1, got multiInstanceLoopCharacteristics in v2, unmapped
// after
migrationBuilder.addActivityMapping("miCallActivity", "miCallActivity");
Defensive patterns

Strategy: validation

Validate before calling

boolean oldMI = ((CallActivity) oldModel.getFlowElement(id)).hasMultiInstanceLoopCharacteristics();
boolean newMI = ((CallActivity) newModel.getFlowElement(id)).hasMultiInstanceLoopCharacteristics();
if (oldMI ^ newMI) { /* explicit mapping required */ }

Try / catch

try {
    migrationBuilder.migrate(instanceId);
} catch (FlowableException e) {
    if (e.getMessage().contains("loop characteristics differs")) {
        // add explicit mapping or keep MI configuration identical
    }
}

Prevention

When it happens

Trigger: Migrating instances where the call activity had multi-instance characteristics added or removed in the new model (XOR of hasMultiInstanceLoopCharacteristics is true) and no explicit mapping exists.

Common situations: Adding a multi-instance (parallel/sequential) wrapper to a previously simple call activity, or removing it, in a new definition version while instances are mid-flight inside that call activity.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/migration/ProcessInstanceMigrationManagerImpl.java:701

                        if (!childSubProcessExecutionActivityIds.isEmpty()) {
                            runningChildrenNotFullyMapped = true;
                            break;
                        }
                    }
                }

                if (!subProcessActivityMappingsByCallActivityIdAndFromActivityId.containsKey(executionActivityId) || runningChildrenNotFullyMapped) {
                    //If there are running child activities not mapped, the call activity must be equally valid in the new model, the activityId in the new model must refer also to a callActivity with matching callElement
                    FlowElement newModelFlowElement = newModel.getFlowElement(executionActivityId);
                    if (newModelFlowElement == null) {
                        throw new FlowableException("Call activity '" + executionActivityId + "' does not exist in the new model. It must be mapped explicitly for migration (or all its child activities)");
                    }
                    if (newModelFlowElement instanceof CallActivity) {
                        if (!referToSameCalledElement((CallActivity) currentModelFlowElement, (CallActivity) newModelFlowElement)) {
                            throw new FlowableException("Call activity '" + executionActivityId + "' has a different called element in the new model. It must be mapped explicitly for migration (or all its child activities)");
                        }
                        if (((CallActivity) currentModelFlowElement).hasMultiInstanceLoopCharacteristics() ^ ((CallActivity) newModelFlowElement).hasMultiInstanceLoopCharacteristics()) {
                            throw new FlowableException("Call activity '" + executionActivityId + "' loop characteristics differs in new model. It must be mapped explicitly for migration (or all its child activities)");
                        }
                    } else {
                        throw new FlowableException("Call activity '" + executionActivityId + "' is not a Call Activity in the new model. It must be mapped explicitly for migration (or all its child activities)");
                    }
                }
            }

            String flowElementMultiInstanceParentId = getFlowElementMultiInstanceParentId(currentModelFlowElement);
            if (flowElementMultiInstanceParentId != null && mappedFromActivities.contains(flowElementMultiInstanceParentId)) {
                // Add the parent MI execution activity Id to be explicitly mapped...
                if (!executionActivityIdsToMapExplicitly.contains(flowElementMultiInstanceParentId)) {
                    executionActivityIdsToMapExplicitly.add(flowElementMultiInstanceParentId);
                }
                // The root executions are the ones to migrate and are explicitly mapped
                List<ExecutionEntity> miRootExecutions = (List<ExecutionEntity>) executionEntityManager.findInactiveExecutionsByActivityIdAndProcessInstanceId(flowElementMultiInstanceParentId, processInstanceExecution.getId());
                filteredExecutionsByActivityId.put(flowElementMultiInstanceParentId, miRootExecutions);
                
            } else {

View on GitHub (pinned to d6d39ce1c6)