{"record":{"id":"39bef56b6fdf771e","repo":"conductor-oss/conductor","slug":"fork-task-definition-is-not-followed-by-a-join-tas","errorCode":null,"errorMessage":"Fork task definition is not followed by a join task.  Check the blueprint","messagePattern":"Fork task definition is not followed by a join task\\.  Check the blueprint","errorType":"exception","errorClass":"TerminateWorkflowException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/com/netflix/conductor/core/execution/mapper/ForkJoinTaskMapper.java","lineNumber":104,"sourceCode":"\n        tasksToBeScheduled.add(forkTask);\n        List<List<WorkflowTask>> forkTasks = workflowTask.getForkTasks();\n        for (List<WorkflowTask> wfts : forkTasks) {\n            WorkflowTask wft = wfts.get(0);\n            List<TaskModel> tasks2 =\n                    taskMapperContext\n                            .getDeciderService()\n                            .getTasksToBeScheduled(workflowModel, wft, retryCount);\n            tasksToBeScheduled.addAll(tasks2);\n        }\n\n        WorkflowTask joinWorkflowTask =\n                workflowModel\n                        .getWorkflowDefinition()\n                        .getNextTask(workflowTask.getTaskReferenceName());\n\n        if (joinWorkflowTask == null || !joinWorkflowTask.getType().equals(TaskType.JOIN.name())) {\n            throw new TerminateWorkflowException(\n                    \"Fork task definition is not followed by a join task.  Check the blueprint\");\n        }\n        List<TaskModel> joinTask =\n                taskMapperContext\n                        .getDeciderService()\n                        .getTasksToBeScheduled(workflowModel, joinWorkflowTask, retryCount);\n\n        tasksToBeScheduled.addAll(joinTask);\n        return tasksToBeScheduled;\n    }\n}\n","sourceCodeStart":86,"sourceCodeEnd":116,"githubUrl":"https://github.com/conductor-oss/conductor/blob/cf7c3e4a8adfb158be778ab1ec525323c363cd3a/core/src/main/java/com/netflix/conductor/core/execution/mapper/ForkJoinTaskMapper.java#L86-L116","documentation":"Thrown by ForkJoinTaskMapper (for static FORK_JOIN tasks, not dynamic) when the task following the FORK task in the workflow definition is not a JOIN. The mapper calls getNextTask() on the fork's taskReferenceName and checks if the result is a JOIN task. If null (no next task) or the type is not 'JOIN', the workflow is terminated. This is purely a workflow definition error.","triggerScenarios":"A FORK_JOIN task in the workflow definition is followed by a non-JOIN task, or is the last task. The JOIN was omitted, reordered, or given the wrong type string.","commonSituations":"Workflow blueprint was hand-edited and the JOIN after a fork was accidentally deleted. A fork was inserted mid-sequence without adding a corresponding JOIN. The JOIN task type is misspelled or has wrong casing.","solutions":["Add a JOIN task immediately after every FORK_JOIN task in the workflow definition.","Ensure the JOIN task's type field is exactly 'JOIN'.","Validate the workflow definition before registration using the Conductor metadata API or UI.","Check that the forkTasks structure in the definition is well-formed — each branch is a non-empty list."],"exampleFix":"// before\n[\n  { \"type\": \"FORK_JOIN\", \"taskReferenceName\": \"fork1\",\n    \"forkTasks\": [[{\"type\":\"SIMPLE\",\"taskReferenceName\":\"a\"}]] },\n  { \"type\": \"SIMPLE\", \"taskReferenceName\": \"after\" }\n]\n\n// after\n[\n  { \"type\": \"FORK_JOIN\", \"taskReferenceName\": \"fork1\",\n    \"forkTasks\": [[{\"type\":\"SIMPLE\",\"taskReferenceName\":\"a\"}]] },\n  { \"type\": \"JOIN\", \"taskReferenceName\": \"join1\", \"joinOn\": [\"a\"] },\n  { \"type\": \"SIMPLE\", \"taskReferenceName\": \"after\" }\n]","handlingStrategy":"validation","validationCode":"// Validate: FORK_JOIN must be followed by JOIN\nWorkflowDef def = metadataDAO.getWorkflowDef(name, version);\nfor (WorkflowTask wt : def.getTasks()) {\n    if (TaskType.TASK_TYPE_FORK_JOIN.equals(wt.getType())) {\n        WorkflowTask next = def.getNextTask(wt.getTaskReferenceName());\n        if (next == null || !TaskType.JOIN.name().equals(next.getType())) {\n            throw new IllegalStateException(\n                \"FORK_JOIN \" + wt.getTaskReferenceName()\n                + \" must be followed by a JOIN task\");\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pair every FORK_JOIN with a subsequent JOIN task.","Validate the workflow definition structure before registration.","Use the Conductor UI blueprint view to visually verify fork-join pairing."],"tags":["fork-join","join","workflow-definition","terminate","blueprint"],"backgroundTag":null,"analyzedSha":"cf7c3e4a8adfb158be778ab1ec525323c363cd3a","analyzedAt":"2026-08-14T03:33:19.897Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}