flowable/flowable-engine · error · FlowableIllegalArgumentException

Invalid sub-process identifier: identifier already exists…

Error message

Invalid sub-process identifier: identifier already exists in host process definition

What it means

Thrown by DynamicSubProcessJoinInjectUtil.injectSubProcessWithJoin when the id given to the DynamicEmbeddedSubProcessBuilder already exists as a flow element in the host process definition. Injecting a SubProcess with a duplicate element id would corrupt the BPMN model and activity lookup (ids must be unique within a process), so the engine rejects it with FlowableIllegalArgumentException.

Solutions

  1. Choose a unique, non-existent id for the DynamicEmbeddedSubProcessBuilder, e.g. a UUID-based or prefixed name
  2. Check process.getFlowElement(candidateId, true) == null before building the injection
  3. Remove or migrate out previous injections if re-injection duplicated ids
  4. Let the builder generate an id via nextSubProcessId instead of setting one that already exists

Example fix

// before
builder.id("subProcess1"); // already exists in host process
// after
String id = "dynamicSubProcess-" + UUID.randomUUID();
if (process.getFlowElement(id, true) == null) builder.id(id);
Defensive patterns

Strategy: validation

Validate before calling

if (dynamicEmbeddedSubProcessBuilder.getId() != null
    && process.getFlowElement(dynamicEmbeddedSubProcessBuilder.getId(), true) != null) {
    throw new IllegalArgumentException("Sub process id already exists in host process");
}

Type guard

static boolean isUniqueFlowElementId(Process process, String id) {
    return id == null || process.getFlowElement(id, true) == null;
}

Try / catch

try {
    injectSubProcessWithJoin(taskId, builder, process, bpmnModel, def, deployment, ctx);
} catch (FlowableIllegalArgumentException ex) {
    if (ex.getMessage().contains("identifier already exists")) {
        // regenerate id and retry
    } else throw ex;
}

Prevention

When it happens

Trigger: Calling injectSubProcessWithJoin after building a DynamicEmbeddedSubProcessBuilder whose explicit id (or a generated one from nextSubProcessId colliding with existing ids) duplicates an existing flow element id in the host process.

Common situations: Hard-coding a subprocess id like 'subProcess1' that already exists in the BPMN; re-running dynamic injection on a model where a previous injection already added an element with the same id.

Related errors


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

Appendix: source

Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/dynamic/DynamicSubProcessJoinInjectUtil.java:60

import org.flowable.task.service.impl.persistence.entity.TaskEntity;

/**
 * @author Tijs Rademakers
 */
public class DynamicSubProcessJoinInjectUtil extends BaseDynamicSubProcessInjectUtil {
    
    public static void injectSubProcessWithJoin(String taskId, Process process, BpmnModel bpmnModel, DynamicEmbeddedSubProcessBuilder dynamicEmbeddedSubProcessBuilder,
                    ProcessDefinitionEntity originalProcessDefinitionEntity, DeploymentEntity newDeploymentEntity, CommandContext commandContext) {
        
        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);
        TaskEntity taskEntity = processEngineConfiguration.getTaskServiceConfiguration().getTaskService().getTask(taskId);
        FlowElement taskFlowElement = process.getFlowElement(taskEntity.getTaskDefinitionKey(), true);
        if (!(taskFlowElement instanceof UserTask userTask)) {
            throw new FlowableException("No UserTask instance found for task definition key " + taskEntity.getTaskDefinitionKey());
        }

        if (dynamicEmbeddedSubProcessBuilder.getId() != null && process.getFlowElement(dynamicEmbeddedSubProcessBuilder.getId(), true) != null) {
            throw new FlowableIllegalArgumentException("Invalid sub-process identifier: identifier already exists in host process definition");
        }
        
        GraphicInfo elementGraphicInfo = bpmnModel.getGraphicInfo(userTask.getId());
        
        SubProcess parentSubProcess = new SubProcess();
        String subProcessId = dynamicEmbeddedSubProcessBuilder.nextSubProcessId(process.getFlowElementMap());
        parentSubProcess.setId(subProcessId);
        parentSubProcess.setName(userTask.getName());
        
        for (SequenceFlow incomingFlow : userTask.getIncomingFlows()) {
            incomingFlow.setTargetRef(parentSubProcess.getId());
        }
        parentSubProcess.setIncomingFlows(userTask.getIncomingFlows());
        
        for (SequenceFlow outgoingFlow : userTask.getOutgoingFlows()) {
            outgoingFlow.setSourceRef(parentSubProcess.getId());
        }
        parentSubProcess.setOutgoingFlows(userTask.getOutgoingFlows());

View on GitHub (pinned to d6d39ce1c6)