Workflow API
The core building blocks you use inside a workflow method — waiting, checkpointing, sleeping, and identity.
These are the primitives you reach for inside a @WorkflowMethod.
Waiting for data with waitUntil
waitUntil pauses a workflow until a condition becomes true — typically because a
signal updated a @StateField. While waiting, the workflow
consumes no resources: it is not occupying a thread or actively running.
@StateField var isApproved: Boolean? = null
@WorkflowMethod
suspend fun processRequest(request: Request): String {
// Wait up to one day for an approval signal to arrive.
val received = waitUntil({ isApproved != null }, Duration.ofDays(1))
if (!received || isApproved != true) return "Rejected or timed out"
return "Approved"
}
@StateField Boolean isApproved;
@WorkflowMethod(returnType = String.class)
public CompletableFuture<String> processRequest(Request request) {
// Wait up to one day for an approval signal to arrive.
boolean received = waitUntil(() -> isApproved != null, Duration.ofDays(1));
if (!received || !Boolean.TRUE.equals(isApproved)) {
return CompletableFuture.completedFuture("Rejected or timed out");
}
return CompletableFuture.completedFuture("Approved");
}
waitUntil returns true if the condition was met within the timeout, or false if it
timed out. Omit the timeout to wait indefinitely. The condition should reference
@StateField values.
Checkpointing with checkpoint
checkpoint runs a block once and persists its effect, so it is not repeated when the
workflow resumes. Use it for expensive computations, one-time state mutations, and any
non-deterministic code that isn’t a natural action.
@StateField var total: Long = 0
@WorkflowMethod
suspend fun process(): Long {
checkpoint { total = expensiveCalculation() } // runs once
waitUntil({ ready }, Duration.ofHours(1))
return total
}
@StateField long total = 0;
@WorkflowMethod(returnType = Long.class)
public CompletableFuture<Long> process() {
checkpoint(() -> { total = expensiveCalculation(); }); // runs once
waitUntil(() -> ready, Duration.ofHours(1));
return CompletableFuture.completedFuture(total);
}
checkpoint is also the correct way to mutate a @StateField from workflow code when a
signal may mutate the same field — without it, the two writers can race in surprising
ways.
Sleeping
To pause for a fixed duration, use sleep:
sleep(Duration.ofMinutes(30))
sleep(Duration.ofMinutes(30));
It is shorthand for waitUntil({ false }, duration).
Workflow identity
The unique id you started the workflow with is available as this.id inside any workflow
method.
val workflowId = this.id
String workflowId = this.id;
Best practices
- Use
@StateFieldfor data that must survive across waits. - Use
checkpointfor expensive or non-deterministic one-time work, and for state shared with signals. - Combine
waitUntilwith signals to bring in external data. - Keep timeouts meaningful — they are your safety net against waiting forever.