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 workflowModel

View on GitHub (pinned to cf7c3e4a8a)

Solutions

  1. Add a JOIN task immediately after the FORK_JOIN_DYNAMIC task in the workflow definition.
  2. Verify the JOIN task's type field is exactly 'JOIN' (case-sensitive).
  3. Use the Conductor UI or workflow definition validator to check the blueprint before registering it.
  4. 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

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


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/4d379ba9037d07e2. Report an issue: GitHub.