Skip to main content
Context actions are methods available on the Restate Context object (ctx) that provide Restate’s core capabilities. These actions enable durable execution, state management, service communication, and timing control.

Durable steps

Use run to safely wrap any non-deterministic operation, like HTTP calls or database responses, and have Restate persist its result.
// External API call
const apiResult = await ctx.run("fetch-data", async () => {
  const response = await fetch("https://api.example.com/data");
  return response.json();
});

// Database operation
const dbResult = await ctx.run("update-user", () => {
  return updateUserDatabase(userId, { name: "John" });
});

// Idempotency key generation
const id = ctx.rand.uuidv4();
// External API call
String apiResult =
    Restate.run("fetchData", String.class, () -> fetchData("https://api.example.com/data"));

// Database operation
boolean dbResult =
    Restate.run("updateUserDatabase", Boolean.class, () -> updateUserDatabase(userId, user));

// Idempotency key generation
String id = Restate.random().nextUUID().toString();
# External API call
api_result = await ctx.run_typed(
    "fetch-data", fetch_url, url="https://api.example.com/data"
)

# Database operation
db_result = await ctx.run_typed(
    "update-user", update_user_database, id=user_id, data={"name": "John"}
)

# Idempotency key generation
id = ctx.uuid()
// External API call
apiResult, err := restate.Run(ctx, func(ctx restate.RunContext) (string, error) {
  return fetchData("https://api.example.com/data")
})
if err != nil {
  return err
}

// Database operation
success, err := restate.Run(ctx, func(ctx restate.RunContext) (bool, error) {
  return updateUserDatabase(userId, user)
})
if err != nil {
  return err
}

// Idempotency key generation
id := restate.UUID(ctx).String()
Without run(), these operations would produce different results during replay, breaking deterministic recovery.

State management

Available in Virtual Object and Workflow handlers for persistent key-value storage.

Get

Retrieve stored state by key.
// Get with type and default value
const profile = await ctx.get<UserProfile>("profile");
const count = (await ctx.get<number>("count")) ?? 0;
const cart = (await ctx.get<ShoppingCart>("cart")) ?? [];
// Get with type and default value
UserProfile profile = Restate.state().get(PROFILE).orElse(null);
int count = Restate.state().get(COUNT).orElse(0);
ShoppingCart cart = Restate.state().get(CART).orElse(new ShoppingCart());
# Get with type and default value
profile = await ctx.get("profile", type_hint=UserProfile)
count = await ctx.get("count", type_hint=int) or 0
cart = await ctx.get("cart", type_hint=ShoppingCart) or ShoppingCart()
// Get with type and default value
profile, err := restate.Get[UserProfile](ctx, "profile")
if err != nil {
  return err
}

count, err := restate.Get[int](ctx, "count")
if err != nil {
  return err
}

cart, err := restate.Get[ShoppingCart](ctx, "cart")
if err != nil {
  return err
}

Set

Store state that persists across function invocations.
// Store simple values
ctx.set("lastLogin", request.date);
ctx.set("count", count + 1);

// Store complex objects
ctx.set("profile", {
  name: "John Doe",
  email: "[email protected]",
});
// Store simple values
Restate.state().set(COUNT, count + 1);
Restate.state().set(LAST_LOGIN, request.date());

// Store complex objects
Restate.state().set(PROFILE, new UserProfile("John Doe", "[email protected]"));
# Store simple values
ctx.set("lastLogin", request["date"])
ctx.set("count", count + 1)

# Store complex objects
ctx.set("profile", UserProfile(name="John Doe", email="[email protected]"))
// Store simple values
restate.Set(ctx, "lastLogin", request.Date)
restate.Set(ctx, "count", count+1)

// Store complex objects
restate.Set(ctx, "profile", UserProfile{
  Name:  "John Doe",
  Email: "[email protected]",
})

Clear

State is retained indefinitely for Virtual Objects, or for the configured retention period for Workflows. To clear state:
// Clear specific keys
ctx.clear("shoppingCart");
ctx.clear("sessionToken");

// Clear all user data
ctx.clearAll();
// Clear specific keys
Restate.state().clear(StateKey.of("shoppingCart", ShoppingCart.class));
Restate.state().clear(StateKey.of("sessionToken", String.class));

// Clear all user data
Restate.state().clearAll();
# Clear specific keys
ctx.clear("shoppingCart")
ctx.clear("sessionToken")

# Clear all user data
ctx.clear_all()

Service communication

Request-response calls

Make request-response calls to other services. Your function waits for the result.
// Call another service
const validation = await ctx
  .serviceClient(ValidationService)
  .validateOrder(order);

// Call Virtual Object function
const profile = await ctx.objectClient(UserAccount, userId).getProfile();

// Submit Workflow
const result = await ctx
  .workflowClient(OrderWorkflow, orderId)
  .run(order);
// Call another service
var validation =
    Restate.serviceHandle(ValidationService.class)
        .call(ValidationService::validateOrder, req.order())
        .await();

// Call Virtual Object function
var profile =
    Restate.virtualObjectHandle(UserAccount.class, req.userId())
        .call(UserAccount::getProfile)
        .await();

// Submit Workflow
var result =
    Restate.workflowHandle(OrderWorkflow.class, req.orderId())
        .call(OrderWorkflow::run, req.order())
        .await();
# Call another service
from .validation_service import validate_order

validation = await ctx.service_call(validate_order, order)

# Call Virtual Object function
from .user_account import get_profile

profile: UserProfile = await ctx.object_call(get_profile, key=user_id, arg=None)

# Submit Workflow
from .order_workflow import run

result = await ctx.workflow_call(run, key=order_id, arg=order)
// Call another service
validation, err := restate.Service[bool](ctx, "ValidationService", "ValidateOrder").Request(order)
if err != nil {
  return err
}

// Call Virtual Object function
profile, err := restate.Object[UserProfile](ctx, "UserAccount", userId, "GetProfile").Request(restate.Void{})
if err != nil {
  return err
}

// Submit Workflow
_, err = restate.Workflow[restate.Void](ctx, "OrderWorkflow", orderId, "Run").Request(order)
if err != nil {
  return err
}

Sending messages

Make one-way calls that don’t return results. Your function continues immediately.
// Fire-and-forget notification
ctx
  .serviceSendClient(NotificationService)
  .sendEmail({ userId, message: "Welcome!" });

// Background analytics
ctx
  .serviceSendClient(AnalyticsService)
  .recordEvent({ kind: "user_signup", userId });

// Cleanup task
ctx.objectSendClient(ShoppingCartObject, userId).emtpyExpiredCart();
// Fire-and-forget notification
Restate.serviceHandle(NotificationService.class)
    .send(NotificationService::sendEmail, new EmailRequest(userId, "Welcome!"));

// Background analytics
Restate.serviceHandle(AnalyticsService.class).send(AnalyticsService::recordEvent, event);

// Cleanup task
Restate.virtualObjectHandle(ShoppingCartObject.class, userId)
    .send(ShoppingCartObject::emptyExpiredCart);
# Fire-and-forget notification
from .notification_service import send_email

ctx.service_send(send_email, {"userId": user_id, "message": "Welcome!"})

# Background analytics
from .analytics_service import record_event

ctx.service_send(record_event, {"kind": "user_signup", "userId": user_id})

# Cleanup task
from .shopping_cart_object import empty_expired_cart

ctx.object_send(empty_expired_cart, key=user_id, arg=None)
// Fire-and-forget notification
restate.ServiceSend(ctx, "NotificationService", "SendEmail").Send(message)

// Background analytics
restate.ServiceSend(ctx, "AnalyticsService", "RecordEvent").Send(event)

// Cleanup task
restate.ObjectSend(ctx, "ShoppingCartObject", userId, "EmptyExpiredCart").Send(restate.Void{})

Delayed messages

Schedule handlers to run in the future.
// Schedule reminder for tomorrow
ctx.serviceSendClient(ReminderService).sendReminder(
  { userId, message },
  sendOpts({
    delay: { days: 1 },
  })
);
// Schedule reminder for tomorrow
Restate.serviceHandle(ReminderService.class)
    .send(ReminderService::sendReminder, reminderRequest, Duration.ofDays(1));
# Schedule reminder for tomorrow
from .notification_service import send_reminder

ctx.service_send(
    send_reminder,
    {"userId": user_id, "message": message},
    send_delay=timedelta(days=1),
)
// Schedule reminder for tomorrow
restate.ServiceSend(ctx, "ReminderService", "SendReminder").Send(
  message,
  restate.WithDelay(24*time.Hour),
)

Durable timers and timeouts

Pause function execution for a specific duration.
// Sleep for specific duration
await ctx.sleep({ minutes: 5 }); // 5 minutes

// Wait for action or timeout
const result = await ctx
  .workflowClient(OrderWorkflow, orderId)
  .run(order)
  .orTimeout({ minutes: 5 });
// Sleep for specific duration
Restate.sleep(Duration.ofMinutes(5)); // 5 minutes

// Wait for action or timeout
try {
  Restate.workflowHandle(OrderWorkflow.class, req.orderId())
      .call(OrderWorkflow::run, req.order())
      .await(Duration.ofMinutes(5));
} catch (TimeoutException e) {
  // Handle timeout
}
# Sleep for specific duration
await ctx.sleep(timedelta(minutes=5))  # 5 minutes

# Wait for action or timeout
from .order_workflow import run

match await restate.select(
    result=ctx.workflow_call(run, key=order_id, arg=order),
    timeout=ctx.sleep(timedelta(minutes=5)),
):
    case ["result", result]:
        return result
    case _:
        print("Order processing timed out")
// Sleep for specific duration
if err := restate.Sleep(ctx, 5*time.Minute); err != nil {
  return err
}

// Wait for action or timeout
sleepFuture := restate.After(ctx, 5*time.Minute)
callFuture := restate.Workflow[restate.Void](ctx, "OrderWorkflow", orderId, "Run").RequestFuture(order)

fut, err := restate.WaitFirst(ctx, sleepFuture, callFuture)
if err != nil {
  return err
}
switch fut {
case sleepFuture:
  if err := sleepFuture.Done(); err != nil {
    return err
  }
  // Timeout occurred
case callFuture:
  if _, err := callFuture.Response(); err != nil {
    return err
  }
  // Call completed
}
Handlers consume no resources while sleeping and resume at exactly the right time, even across restarts (see suspensions).

Workflow events

Use durable promises to wait for external events or human input in your workflows. Create promises that external systems can resolve to send data to your workflow.
// Wait for external event
const paymentResult = await ctx.promise<PaymentResult>(
  "payment-completed"
);

// Wait for human approval
const approved = await ctx.promise<boolean>("manager-approval");

// Wait for multiple events
const [payment, inventory] = await RestatePromise.all([
  ctx.promise<PaymentResult>("payment").get(),
  ctx.promise<InventoryResult>("inventory").get(),
]);
// Wait for external event
PaymentResult paymentResult = Restate.promise(PAYMENT_COMPLETED).future().await();

// Wait for human approval
Boolean approved = Restate.promise(MANAGER_APPROVAL).future().await();

// Wait for multiple events
var paymentFuture = Restate.promise(PAYMENT).future();
var inventoryFuture = Restate.promise(INVENTORY).future();
PaymentResult payment = paymentFuture.await();
InventoryResult inventory = inventoryFuture.await();
# Wait for external event
payment_result = await ctx.promise(
    "payment-completed", type_hint=PaymentResult
).value()

# Wait for human approval
approved = await ctx.promise("manager-approval", type_hint=bool).value()

# Wait for multiple events using gather
payment_promise = ctx.promise("payment", type_hint=PaymentResult)
inventory_promise = ctx.promise("inventory", type_hint=InventoryConfirmation)
payment, inventory = await restate.gather(
    payment_promise.value(), inventory_promise.value()
)
// Wait for external event
paymentResult, err := restate.Promise[PaymentResult](ctx, "payment-completed").Result()
if err != nil {
  return err
}

// Wait for human approval
approved, err := restate.Promise[bool](ctx, "manager-approval").Result()
if err != nil {
  return err
}

// Wait for multiple events
paymentFuture := restate.Promise[PaymentResult](ctx, "payment")
inventoryFuture := restate.Promise[InventoryResult](ctx, "inventory")

payment, err := paymentFuture.Result()
if err != nil {
  return err
}

inventory, err := inventoryFuture.Result()
if err != nil {
  return err
}
Resolve promises from signal handlers.
// In a signal function
confirmPayment: async (
  ctx: WorkflowSharedContext,
  result: PaymentResult
) => {
  await ctx.promise("payment-completed").resolve(result);
},

// In a signal function
approveRequest: async (ctx: WorkflowSharedContext, approved: boolean) => {
  await ctx.promise("manager-approval").resolve(approved);
},
// In a signal function
@Shared
public void confirmPayment(PaymentResult result) {
  Restate.promiseHandle(PAYMENT_COMPLETED).resolve(result);
}

// In a signal function
@Shared
public void approveRequest(Boolean approved) {
  Restate.promiseHandle(MANAGER_APPROVAL).resolve(approved);
}
# In a signal function
@workflow_example_workflow.handler()
async def confirm_payment(ctx: WorkflowSharedContext, result: PaymentResult) -> None:
    await ctx.promise("payment-completed", type_hint=PaymentResult).resolve(result)


# In a signal function
@workflow_example_workflow.handler()
async def approve_request(ctx: WorkflowSharedContext, approved: bool) -> None:
    await ctx.promise("manager-approval", type_hint=bool).resolve(approved)
// In a signal function
func (WorkflowExample) ConfirmPayment(ctx restate.WorkflowSharedContext, result PaymentResult) error {
  if err := restate.Promise[PaymentResult](ctx, "payment-completed").Resolve(result); err != nil {
    return err
  }
  return nil
}

// In a signal function
func (WorkflowExample) ApproveRequest(ctx restate.WorkflowSharedContext, approved bool) error {
  if err := restate.Promise[bool](ctx, "manager-approval").Resolve(approved); err != nil {
    return err
  }
  return nil
}
To implement a similar pattern in Basic Services or Virtual Objects, have a look at awakeables (TS/Java/Kotlin/Go/Python).