{"record":{"id":"75afc5b9e8ec4d43","repo":"conductor-oss/conductor","slug":"tasks-could-not-be-dynamically-forked-due-to-inval","errorCode":null,"errorMessage":"Tasks could not be dynamically forked due to invalid input: %s","messagePattern":"Tasks could not be dynamically forked due to invalid input: (.+?)","errorType":"exception","errorClass":"TerminateWorkflowException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/com/netflix/conductor/core/execution/mapper/ForkJoinDynamicTaskMapper.java","lineNumber":213,"sourceCode":"            for (WorkflowTask dynForkTask :\n                    dynForkTasks) { // TODO this is a cyclic dependency, break it out using function\n                // composition\n                try {\n                    Map<String, Object> forkedTaskInput =\n                            tasksInput.get(dynForkTask.getTaskReferenceName());\n                    if (dynForkTask.getInputParameters() == null) {\n                        dynForkTask.setInputParameters(new HashMap<>());\n                    }\n                    if (forkedTaskInput == null) {\n                        forkedTaskInput = new HashMap<>();\n                    }\n                    dynForkTask.getInputParameters().putAll(forkedTaskInput);\n                } catch (Exception e) {\n                    String reason =\n                            String.format(\n                                    \"Tasks could not be dynamically forked due to invalid input: %s\",\n                                    e.getMessage());\n                    throw new TerminateWorkflowException(reason);\n                }\n                List<TaskModel> forkedTasks =\n                        taskMapperContext\n                                .getDeciderService()\n                                .getTasksToBeScheduled(workflowModel, dynForkTask, retryCount);\n                if (forkedTasks == null || forkedTasks.isEmpty()) {\n                    Optional<String> existingTaskRefName =\n                            workflowModel.getTasks().stream()\n                                    .filter(\n                                            runningTask ->\n                                                    runningTask\n                                                                    .getStatus()\n                                                                    .equals(\n                                                                            TaskModel.Status\n                                                                                    .IN_PROGRESS)\n                                                            || runningTask.getStatus().isTerminal())\n                                    .map(TaskModel::getReferenceTaskName)\n                                    .filter(","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/conductor-oss/conductor/blob/cf7c3e4a8adfb158be778ab1ec525323c363cd3a/core/src/main/java/com/netflix/conductor/core/execution/mapper/ForkJoinDynamicTaskMapper.java#L195-L231","documentation":"Thrown by ForkJoinDynamicTaskMapper during the processing of dynamic fork task inputs. When merging forkedTaskInput into the dynamic task's inputParameters, any exception (ClassCastException, NullPointerException, etc.) is caught and wrapped into a TerminateWorkflowException with the original exception's message. This terminates the workflow — the fork cannot proceed with corrupt input.","triggerScenarios":"A FORK_JOIN_DYNAMIC task whose dynamic task input map has unexpected types — e.g., forkedTaskInput is a List instead of a Map, or a ClassCastException occurs when calling putAll(). The input key referenced by the dynamic task's taskReferenceName does not map to a Map<String, Object>.","commonSituations":"The input parameter named by the forked task reference contains a non-map value at runtime. Upstream task output schema changed so a previously-map field is now a scalar. JSON deserialization produced a LinkedHashMap where a specific subtype was expected.","solutions":["Inspect the full ForkJoinDynamicTaskMapper error in the server log — the original exception's message is embedded in the TerminateWorkflowException reason.","Verify that every entry in the dynamic fork tasks input map (keyed by taskReferenceName) is itself a Map<String, Object>.","Fix the upstream task or input mapping so each forked task's input is a proper JSON object."],"exampleFix":"// before — forkedTasksInput contains a scalar for ref 't1'\n{\n  \"forkedTasks\": [{ \"name\": \"task1\", \"taskReferenceName\": \"t1\" }],\n  \"forkedTasksInput\": {\n    \"t1\": \"should-be-a-map\"\n  }\n}\n\n// after\n{\n  \"forkedTasks\": [{ \"name\": \"task1\", \"taskReferenceName\": \"t1\" }],\n  \"forkedTasksInput\": {\n    \"t1\": { \"param\": \"value\" }\n  }\n}","handlingStrategy":"validation","validationCode":"// Validate forked task inputs are maps before processing\nfor (WorkflowTask dynForkTask : dynForkTasks) {\n    Object forkedInput = tasksInput.get(dynForkTask.getTaskReferenceName());\n    if (forkedInput != null && !(forkedInput instanceof Map)) {\n        throw new IllegalArgumentException(\n            \"Input for \" + dynForkTask.getTaskReferenceName()\n            + \" must be a Map, got: \" + forkedInput.getClass());\n    }\n}","typeGuard":"private boolean allInputsAreMaps(Map<String, ?> tasksInput, List<WorkflowTask> tasks) {\n    return tasks.stream()\n        .map(WorkflowTask::getTaskReferenceName)\n        .map(tasksInput::get)\n        .allMatch(v -> v == null || v instanceof Map);\n}","tryCatchPattern":null,"preventionTips":["Ensure each forked task's input in the dynamic fork input map is a JSON object.","Validate the dynamic fork input payload shape before the FORK_JOIN_DYNAMIC task executes.","Use an INLINE task to normalize input shapes upstream."],"tags":["fork-join-dynamic","terminate","input-mapping","type-mismatch"],"backgroundTag":null,"analyzedSha":"cf7c3e4a8adfb158be778ab1ec525323c363cd3a","analyzedAt":"2026-08-14T03:33:19.897Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}