Skip to main content
Your Restate handler can call other handlers in three ways:
To call a service from an external application, see the HTTP, Kafka, or SDK Clients documentation.

Request-response calls

To call a Restate handler and wait for its result, use one of two client styles:
  • Simple client (Restate.service(...) / Restate.virtualObject(...) / Restate.workflow(...) in Java; service<T>() / virtualObject<T>(key) / workflow<T>(key) in Kotlin): the call is awaited inline and returns the result directly. Use it for straightforward request-response.
  • Handle-based client (Restate.serviceHandle(...) etc. in Java; toService<T>() etc. in Kotlin, called with a method reference): returns a DurableFuture you await explicitly. Use it for invocation options (e.g. idempotency keys), timeouts, or concurrency.
Use a generic call when you don’t have the typed interface, or need dynamic service/handler names:
After a workflow’s run handler completes, other handlers can still be called for up to 24 hours (default). Update this via the service configuration.
Request-response calls between exclusive handlers of Virtual Objects may lead to deadlocks:
  • Cross deadlock: A → B and B → A (same keys).
  • Cycle deadlock: A → B → C → A.
Use the UI or CLI to cancel and unblock deadlocked invocations.

Sending messages

To send a message to another Restate handler without waiting for a response:
Restate handles message delivery and retries, so the handler can complete and return without waiting for the message to be processed. Use a generic send when you don’t have the typed interface:
Calls to a Virtual Object execute in order of arrival, serially. Example:
Call A is guaranteed to execute before B. However, other invocations may interleave between A and B.

Delayed messages

To send a message after a delay, use .send() with the Duration as an additional parameter:
Or with a generic send:
Learn how this is different from sleeping and then sending a message.

Using an idempotency key

To prevent duplicate executions of the same call, add an idempotency key:
Restate automatically deduplicates calls made during the same handler execution, so there’s no need to provide an idempotency key in that case. However, if multiple handlers might call the same service independently, you can use an idempotency key to ensure deduplication across those calls.

Flow control: scope and limit key

Scope and limit key are a preview feature and require restate-server 1.7 with flow control enabled.
Flow control caps how many invocations run concurrently within a scope, with optional hierarchical limit keys. Route a call into a scope with Restate.scope(...) (Java) or scope(...) (Kotlin), and add a limit key for a finer, per subgroup limit within that scope:
You can read the scope and limit key an invocation was submitted with from the request:
See Flow control for how scopes, limit keys, and the concurrency rule book work.

Attach to an invocation

To wait for or get the result of a previously sent message:
  • With an idempotency key: Wait for completion and retrieve the result.
  • Without an idempotency key: Can only wait, not retrieve the result.

Cancel an invocation

To cancel a running handler:

See also

  • SDK Clients: Call Restate services from external applications
  • Error Handling: Handle failures and terminal errors in service calls
  • Durable Timers: Implement timeouts for your service calls
  • Serialization: Customize how data is serialized between services
  • Sagas: Roll back or compensate for canceled service calls.