Run independent tasks in parallel, then converge before continuing. Always pair FORK_JOIN with a JOIN that lists every parallel branch’s terminal taskReferenceName.
┌─→ branch_a_task ─┐
fork ──┤ ├─→ join ─→ next
└─→ branch_b_task ─┘
See workflows/fork-join.json. Key tasks:
{
"name": "fork", "taskReferenceName": "fork", "type": "FORK_JOIN",
"forkTasks": [
[{"name": "fetch_inventory", "taskReferenceName": "inventory", "type": "HTTP", "inputParameters": {"http_request": {"uri": "${workflow.input.inventoryUrl}", "method": "GET"}}}],
[{"name": "fetch_pricing", "taskReferenceName": "pricing", "type": "HTTP", "inputParameters": {"http_request": {"uri": "${workflow.input.pricingUrl}", "method": "GET"}}}]
]
},
{
"name": "join", "taskReferenceName": "join", "type": "JOIN",
"joinOn": ["inventory", "pricing"]
}
conductor workflow create examples/workflows/fork-join.json
conductor workflow start -w parallel_fetch -i '{"inventoryUrl": "https://...", "pricingUrl": "https://..."}'
Each branch’s output is available as ${branchRef.output...} in tasks after the JOIN. The JOIN task’s own output aggregates branch outputs by reference name:
${join.output.inventory.response.body}
${join.output.pricing.response.body}
forkTasks are arrays of arrays — one inner array per parallel branch. Each inner array can itself be a sequence of tasks.joinOn must list the last task’s taskReferenceName from each branch.optional: true.FORK_JOIN_DYNAMIC (see workflow-definition.md).