conductor-oss/conductor · error · TerminateWorkflowException
Dynamic join definition is not followed by a join task. Che
Error message
Dynamic join definition is not followed by a join task. Check the workflow definition.
What it means
Thrown when the task following a FORK_JOIN_DYNAMIC task in the workflow definition is not a JOIN task, or when there is no following task at all. Conductor requires every fork (static or dynamic) to be followed by a JOIN so the workflow can wait for all parallel branches to complete. Without a JOIN, there is no synchronization point. This is a workflow definition (blueprint) error, not a runtime data error.
Source
Thrown at core/src/main/java/com/netflix/conductor/core/execution/mapper/ForkJoinDynamicTaskMapper.java:268
mappedTasks.addAll(forkedTasks);
// Get the last of the dynamic tasks so that the join can be performed once this
// task is
// done
TaskModel last = forkedTasks.get(forkedTasks.size() - 1);
joinOnTaskRefs.add(last.getReferenceTaskName());
}
}
// From the workflow definition get the next task and make sure that it is a JOIN task.
// The dynamic fork tasks need to be followed by a join task
WorkflowTask joinWorkflowTask =
workflowModel
.getWorkflowDefinition()
.getNextTask(workflowTask.getTaskReferenceName());
if (joinWorkflowTask == null || !joinWorkflowTask.getType().equals(TaskType.JOIN.name())) {
throw new TerminateWorkflowException(
"Dynamic join definition is not followed by a join task. Check the workflow definition.");
}
// Create Join task
HashMap<String, Object> joinInput = new HashMap<>(joinWorkflowTask.getInputParameters());
joinInput.put("joinOn", joinOnTaskRefs);
TaskModel joinTask = createJoinTask(workflowModel, joinWorkflowTask, joinInput);
mappedTasks.add(joinTask);
return mappedTasks;
}
/**
* This method creates a FORK task and adds the list of dynamic fork tasks keyed by
* "forkedTaskDefs" and their names keyed by "forkedTasks" into {@link TaskModel#getInputData()}
*
* @param taskMapperContext: The {@link TaskMapperContext} which wraps workflowTask, workflowDef
* and workflowModelView on GitHub (pinned to cf7c3e4a8a)
Solutions
- Add a JOIN task immediately after the FORK_JOIN_DYNAMIC task in the workflow definition.
- Verify the JOIN task's type field is exactly 'JOIN' (case-sensitive).
- Use the Conductor UI or workflow definition validator to check the blueprint before registering it.
- If the fork is at the end of the workflow and no join is needed, reconsider the workflow structure — a fork without a join is not valid.
Example fix
// before
[
{ "type": "FORK_JOIN_DYNAMIC", "taskReferenceName": "dyn_fork", ... },
{ "type": "SIMPLE", "taskReferenceName": "next_task", ... }
]
// after
[
{ "type": "FORK_JOIN_DYNAMIC", "taskReferenceName": "dyn_fork", ... },
{ "type": "JOIN", "taskReferenceName": "dyn_join", "joinOn": ["..."] },
{ "type": "SIMPLE", "taskReferenceName": "next_task", ... }
] Defensive patterns
Strategy: validation
Validate before calling
// Validate workflow definition: FORK_JOIN_DYNAMIC must be followed by JOIN
WorkflowDef def = metadataDAO.getWorkflowDef(name, version);
for (WorkflowTask wt : def.getTasks()) {
if (TaskType.TASK_TYPE_FORK_JOIN_DYNAMIC.equals(wt.getType())) {
WorkflowTask next = def.getNextTask(wt.getTaskReferenceName());
if (next == null || !TaskType.JOIN.name().equals(next.getType())) {
throw new IllegalStateException(
"FORK_JOIN_DYNAMIC " + wt.getTaskReferenceName()
+ " must be followed by a JOIN task");
}
}
} Prevention
- Always add a JOIN task after every FORK_JOIN_DYNAMIC in the definition.
- Use a workflow definition validator before registering metadata.
- Review the blueprint in the Conductor UI to visually confirm fork-join pairing.
When it happens
Trigger: A workflow definition has a FORK_JOIN_DYNAMIC task as the last task, or its getNextTask() returns a task whose type is not 'JOIN'. The JOIN task was accidentally removed or renamed during a workflow definition update.
Common situations: Workflow definition edited manually and the trailing JOIN was omitted. A FORK_JOIN_DYNAMIC was inserted in the middle of a linear sequence without adding a corresponding JOIN. The JOIN task type is specified as 'Join' (wrong case) instead of 'JOIN'.
Related errors
- Fork task definition is not followed by a join task. Check
- Tasks could not be dynamically forked due to invalid input:
- No dynamic tasks could be created for the Workflow: %s, Dyna
- Input to the dynamically forked tasks is not a map -> expect
- Input '%s' is invalid. Cannot deserialize a list of Workflow
AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14).
Data as JSON: /api/errors/4d379ba9037d07e2.
Report an issue: GitHub.