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 @StateField for data that must survive across waits.
  • Use checkpoint for expensive or non-deterministic one-time work, and for state shared with signals.
  • Combine waitUntil with signals to bring in external data.
  • Keep timeouts meaningful — they are your safety net against waiting forever.