conductor-oss/conductor · error · TerminateWorkflowException
Tasks could not be dynamically forked due to invalid input
Error message
Tasks could not be dynamically forked due to invalid input: %s
What it means
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.
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.
Example fix
// before — forkedTasksInput contains a scalar for ref 't1'
{
"forkedTasks": [{ "name": "task1", "taskReferenceName": "t1" }],
"forkedTasksInput": {
"t1": "should-be-a-map"
}
}
// after
{
"forkedTasks": [{ "name": "task1", "taskReferenceName": "t1" }],
"forkedTasksInput": {
"t1": { "param": "value" }
}
} Defensive patterns
Strategy: validation
Validate before calling
// Validate forked task inputs are maps before processing
for (WorkflowTask dynForkTask : dynForkTasks) {
Object forkedInput = tasksInput.get(dynForkTask.getTaskReferenceName());
if (forkedInput != null && !(forkedInput instanceof Map)) {
throw new IllegalArgumentException(
"Input for " + dynForkTask.getTaskReferenceName()
+ " must be a Map, got: " + forkedInput.getClass());
}
} Type guard
private boolean allInputsAreMaps(Map<String, ?> tasksInput, List<WorkflowTask> tasks) {
return tasks.stream()
.map(WorkflowTask::getTaskReferenceName)
.map(tasksInput::get)
.allMatch(v -> v == null || v instanceof Map);
} Prevention
- 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.
When it happens
Trigger: 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>.
Common situations: 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.
Related errors
- Input to the dynamically forked tasks is not a map ->…
- Dynamic join definition is not followed by a join task. …
- Dynamic tasks could not be created. The value of
- Input has to be a JSON object
- Input ' ' is invalid. Cannot deserialize a list of Workflow…
AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14).
Data as JSON: /api/errors/75afc5b9e8ec4d43.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/com/netflix/conductor/core/execution/mapper/ForkJoinDynamicTaskMapper.java:213
for (WorkflowTask dynForkTask :
dynForkTasks) { // TODO this is a cyclic dependency, break it out using function
// composition
try {
Map<String, Object> forkedTaskInput =
tasksInput.get(dynForkTask.getTaskReferenceName());
if (dynForkTask.getInputParameters() == null) {
dynForkTask.setInputParameters(new HashMap<>());
}
if (forkedTaskInput == null) {
forkedTaskInput = new HashMap<>();
}
dynForkTask.getInputParameters().putAll(forkedTaskInput);
} catch (Exception e) {
String reason =
String.format(
"Tasks could not be dynamically forked due to invalid input: %s",
e.getMessage());
throw new TerminateWorkflowException(reason);
}
List<TaskModel> forkedTasks =
taskMapperContext
.getDeciderService()
.getTasksToBeScheduled(workflowModel, dynForkTask, retryCount);
if (forkedTasks == null || forkedTasks.isEmpty()) {
Optional<String> existingTaskRefName =
workflowModel.getTasks().stream()
.filter(
runningTask ->
runningTask
.getStatus()
.equals(
TaskModel.Status
.IN_PROGRESS)
|| runningTask.getStatus().isTerminal())
.map(TaskModel::getReferenceTaskName)
.filter(View on GitHub (pinned to cf7c3e4a8a)