Skip to main content

Child workflows

Besides activities, a workflow can also orchestrate other workflows. The orchestrating workflow is the parent and the workflow it schedules is the child. A child has a lifecycle the parent shapes at every stage: the parent starts it, watches it, communicates with it, and decides its fate when the parent itself closes or is canceled.

Child workflows run independently of that relationship in every other respect: each has its own event history, its own timeouts and retry policy, and can run on a different task list and worker pool than the parent. This page follows a child from start to finish.


When to use a child workflow

A child workflow is not always the right tool. Pick the lightest option that fits the work:

OptionWhen to use it
ActivityThe work is a single non-deterministic operation, such as an API call, a DB write, or sending an email. Activities are retried independently and have the lowest overhead.
Child WorkflowYou need a reusable, self-contained orchestration with its own event history, timeouts, and retry policy, but whose lifecycle is still tied to the parent.
Standalone WorkflowThe process is fully independent and shouldn't share the parent's lifecycle. Start it as its own top-level execution with a WorkflowClient instead.

Starting a child

Workflow.newChildWorkflowStub returns a client-side stub that implements a child workflow interface. It takes a child workflow type and optional ChildWorkflowOptions. Options are needed only to override the timeouts and task list defined on the child's @WorkflowMethod annotation or inherited from the parent.

The first call on the stub must be to the method annotated with @WorkflowMethod. As with activities, the call can be synchronous or asynchronous via Async#function or Async#procedure. A synchronous call blocks until the child completes; an asynchronous call returns a Promise.

public static class GreetingWorkflowImpl implements GreetingWorkflow {

@Override
public String getGreeting(String name) {
// Workflows are stateful, so a new stub must be created for each new child.
GreetingChild child = Workflow.newChildWorkflowStub(GreetingChild.class);

// Non-blocking call that returns immediately.
// Use child.composeGreeting("Hello", name) to call synchronously.
Promise<String> greeting = Async.function(child::composeGreeting, "Hello", name);
// Do something else here.
return greeting.get(); // blocks waiting for the child to complete.
}
}

Starting a child only schedules it. The child is not actually created until the parent yields control back to Cadence. This matters when the parent may finish quickly (see When the parent closes).


Monitoring and communicating

An asynchronous call returns a Promise; block on it only when the parent needs the result.

To let a child keep running while the parent returns, block until the child has started first. Otherwise the parent may complete before the child is scheduled:

private String demoAsyncChildRun(String name) {
GreetingChild child = Workflow.newChildWorkflowStub(GreetingChild.class);
// Non-blocking call that initiates the child workflow.
Async.function(child::composeGreeting, "Hello", name);
// Block until the child has started, otherwise it may not start
// because the parent completes first.
Promise<WorkflowExecution> childPromise = Workflow.getWorkflowExecution(child);
childPromise.get();
return "let child run, parent just return";
}

Run children in parallel by creating a separate stub per child and starting each asynchronously. The children run concurrently; block on each Promise when you need its result.

GreetingChild child1 = Workflow.newChildWorkflowStub(GreetingChild.class);
Promise<String> greeting1 = Async.function(child1::composeGreeting, "Hello", name);

GreetingChild child2 = Workflow.newChildWorkflowStub(GreetingChild.class);
Promise<String> greeting2 = Async.function(child2::composeGreeting, "Bye", name);

return "First: " + greeting1.get() + ", second: " + greeting2.get();

Signal a running child by calling a method annotated with @SignalMethod on the stub after the async call returns.

public interface GreetingChild {
@WorkflowMethod
String composeGreeting(String greeting, String name);

@SignalMethod
void updateName(String name);
}

// In the parent:
GreetingChild child = Workflow.newChildWorkflowStub(GreetingChild.class);
Promise<String> greeting = Async.function(child::composeGreeting, "Hello", name);
child.updateName("Cadence");
return greeting.get();
Queries cannot be issued from workflow code

Calling @QueryMethod methods on a child from within workflow code is not supported. Run queries from an activity or external process using a WorkflowClient stub instead.


When the parent closes

ParentClosePolicy decides what Cadence does to a still-running child when the parent closes, whether the parent completes, fails, times out, or is terminated. It is set per child on ChildWorkflowOptions.

PolicyJava enumBehavior
Terminate (default)ParentClosePolicy.TERMINATEThe child is terminated immediately when the parent closes.
Request cancelParentClosePolicy.REQUEST_CANCELA cancellation request is sent to the child, giving it a chance to run cleanup before exiting.
AbandonParentClosePolicy.ABANDONThe child keeps running independently after the parent closes.

Use abandon for children that are meant to outlive their parent. For example, a long-running detached process started by a short-lived orchestrator.

import com.uber.cadence.ParentClosePolicy;

ChildWorkflowOptions options = new ChildWorkflowOptions.Builder()
.setWorkflowId("detached-child")
.setParentClosePolicy(ParentClosePolicy.ABANDON)
.build();

GreetingChild child = Workflow.newChildWorkflowStub(GreetingChild.class, options);
Async.function(child::composeGreeting, "Hello", name);

// Block until the child has started before the parent returns.
Workflow.getWorkflowExecution(child).get();
Wait for an abandoned child to start before the parent returns

A child is only scheduled once the parent yields control to Cadence. If the parent completes before the child has started, the child may never run, which defeats the purpose of ABANDON. Always block on Workflow.getWorkflowExecution(child).get() before returning from the parent, as shown in Monitoring and communicating.

Default is terminate

If you do not call setParentClosePolicy, children are terminated when the parent closes. Set it explicitly whenever a child should survive or shut down gracefully.


Cancelling a child

Closing is not the only way a child ends early. A child runs inside a CancellationScope; cancel the scope to request cancellation of every child started within it.

CancellationScope scope = Workflow.newCancellationScope(() -> {
GreetingChild child = Workflow.newChildWorkflowStub(GreetingChild.class);
Async.function(child::composeGreeting, "Hello", name);
});
scope.run();

// Request cancellation of everything started inside the scope, including the child.
scope.cancel();

Child workflow options

ChildWorkflowOptions controls the child's identity, routing, and lifecycle. Most fields are optional and inherit sensible defaults from the parent or the child's annotations.

Builder methodRequiredPurpose
setWorkflowIdNoStable ID for the child execution. Generated automatically if omitted.
setDomainNoRun the child in a different domain than the parent.
setTaskListNoRoute the child to a specific worker pool. Inherits the parent's task list if omitted.
setExecutionStartToCloseTimeoutConditionalMaximum total runtime for the child execution. A value is required, but it can instead be declared with executionStartToCloseTimeoutSeconds on the child's @WorkflowMethod.
setTaskStartToCloseTimeoutNoMaximum time for a single decision task. Defaults to 10 seconds.
setWorkflowIdReusePolicyNoWhether a completed WorkflowId may be reused.
setRetryOptionsNoExponential retry policy applied to the child execution.
setParentClosePolicyNoWhat happens to the child when the parent closes. See When the parent closes.

Samples

Runnable child-workflow samples:

SampleDescriptionCode
Child workflowParent starts a child, waits for its resultHelloChild.java
Child cancellationParent cancels a running childHelloCancelChild.java

References