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
- Choose a unique, non-existent id for the DynamicEmbeddedSubProcessBuilder, e.g. a UUID-based or prefixed name
- Check process.getFlowElement(candidateId, true) == null before building the injection
- Remove or migrate out previous injections if re-injection duplicated ids
- 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
- Generate subprocess ids with a unique prefix/UUID instead of fixed names
- Check getFlowElement(id) before setting the builder id
- Avoid re-injecting the same id into an already-modified model
- Keep an id registry for dynamically added elements per process definition
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
- No UserTask instance found for task definition key " +…
- No multi instance execution found for activity id
- A channel key detection value is required for inbound…
- A channel key detection value is required for the channel…
- A datasource is required for initializing the engine
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)