Skip to main content
The Restate Java/Kotlin SDK is open source and available on GitHub. The Restate SDK lets you implement handlers. Handlers can be part of a Basic Service, a Virtual Object, or a Workflow. This page shows how to define them with the Java/Kotlin SDK.

Prerequisites

  • JDK >= 17 (JDK >= 23 recommended, required for the latest Restate features)

Getting started

Get started quickly with the Java or Kotlin Quickstart.
To start building Restate services, add the SDK dependency to your Maven/Gradle project manifest. The SDK comes in different flavors: Java or Kotlin API, HTTP or Lambda. Choose the one you need depending on the language you want to use and whether you want to deploy the service as an HTTP server or as AWS Lambda.
// For deploying as HTTP service
implementation("dev.restate:sdk-java-http:2.9.0")
// Or for deploying using AWS Lambda
implementation("dev.restate:sdk-java-lambda:2.9.0")
<properties>
    <restate.version>2.9.0</restate.version>
</properties>
<dependencies>
    <!-- For deploying as HTTP service -->
    <dependency>
        <groupId>dev.restate</groupId>
        <artifactId>sdk-java-http</artifactId>
        <version>${restate.version}</version>
    </dependency>
    <!-- Or for deploying using AWS Lambda -->
    <dependency>
        <groupId>dev.restate</groupId>
        <artifactId>sdk-java-lambda</artifactId>
        <version>${restate.version}</version>
    </dependency>
</dependencies>
// For deploying as HTTP service
implementation("dev.restate:sdk-kotlin-http:2.9.0")
// Or for deploying using AWS Lambda
implementation("dev.restate:sdk-kotlin-lambda:2.9.0")
The SDK uses a native library on JDK 23+. To silence the native-access warning printed at startup, enable native access by passing --enable-native-access=ALL-UNNAMED as a JVM argument, or by setting Enable-Native-Access: ALL-UNNAMED in your JAR manifest.
The SDK creates proxies for your services, which requires non-final classes. Kotlin classes are final by default, so apply the Kotlin all-open compiler plugin for the Restate annotations. The Spring Boot Kotlin starter applies this automatically; for a plain Gradle project add:
Kotlin/Gradle
plugins {
    kotlin("plugin.allopen") version "<kotlin-version>"
}

allOpen {
    annotation("dev.restate.sdk.annotation.Service")
    annotation("dev.restate.sdk.annotation.VirtualObject")
    annotation("dev.restate.sdk.annotation.Workflow")
}
Alternatively, use on each Restate annotated class open.
Using Spring Boot? See Spring Boot for the starter dependency, @RestateComponent, dependency injection, and injecting the ingress Client.

Basic Services

Basic Services group related handlers and expose them as callable endpoints:
@Service
public class MyService {
  @Handler
  public String myHandler(String greeting) {
    return greeting + "!";
  }

  public static void main(String[] args) {
    RestateHttpServer.listen(Endpoint.bind(new MyService()));
  }
}
@Service
class MyService {
  @Handler suspend fun myHandler(greeting: String) = "$greeting!"
}

fun main() {
  RestateHttpServer.listen(endpoint { bind(MyService()) })
}
  • Define a service using the @Service and @Handler annotations
  • Each handler can be called at <RESTATE_INGRESS>/MyService/myHandler. To override the service name (default is simple class name), use the annotation @Name.
  • Access Restate’s capabilities (state, calls, side effects, timers, …) through the static methods on the Restate class (Java), or the top-level functions in the dev.restate.sdk.kotlin package (Kotlin).
  • The input parameter (at most one) and return type are optional and can be of any type. See serialization for more details.
  • Create an endpoint to expose the service over HTTP (port 9080 by default).

Virtual Objects

Virtual Objects are services that are stateful and key-addressable — each object instance has a unique ID and persistent state.
@VirtualObject
public class MyObject {

  @Handler
  public String myHandler(String greeting) {
    String objectId = Restate.key();

    return greeting + " " + objectId + "!";
  }

  @Shared
  public String myConcurrentHandler(String input) {
    return "my-output";
  }

  public static void main(String[] args) {
    RestateHttpServer.listen(Endpoint.bind(new MyObject()));
  }
}
@VirtualObject
class MyObject {

  @Handler
  suspend fun myHandler(greeting: String): String {
    val objectKey = objectKey()

    return "$greeting $objectKey!"
  }

  @Shared suspend fun myConcurrentHandler(input: String) = "my-output"
}

fun main() {
  RestateHttpServer.listen(endpoint { bind(MyObject()) })
}
  • Use the @VirtualObject annotation.
  • Each instance is identified by a key, accessible via Restate.key() (Java) or objectKey() (Kotlin).
  • Access the object’s persistent state via Restate.state() (Java) or state() (Kotlin).
  • Virtual Objects can have exclusive and shared handlers.
  • Exclusive handlers (the default) have read/write access to the object state.
  • Shared handlers use the @Shared annotation and have read-only access to the state.

Workflows

Workflows are long-lived processes with a defined lifecycle. They run once per key and are ideal for orchestrating multi-step operations, which require external interaction via signals and queries.
@Workflow
public class MyWorkflow {

  @Workflow
  public String run(String input) {

    // implement workflow logic here

    return "success";
  }

  @Shared
  public String interactWithWorkflow(String input) {
    // implement interaction logic here
    return "my result";
  }

  public static void main(String[] args) {
    RestateHttpServer.listen(Endpoint.bind(new MyWorkflow()));
  }
}
@Workflow
class MyWorkflow {

  @Workflow
  suspend fun run(input: String): String {
    // implement workflow logic here

    return "success"
  }

  @Handler
  suspend fun interactWithWorkflow(input: String): String {
    // implement interaction logic here
    return "my result"
  }
}

fun main() {
  RestateHttpServer.listen(endpoint { bind(MyWorkflow()) })
}
  • Create the workflow by using the @Workflow annotation.
  • Every workflow must include a run handler:
    • This is the main orchestration entry point
    • It runs exactly once per workflow execution
    • Resubmission of the same workflow will fail with “Previously accepted”. The invocation ID can be found in the request header x-restate-id.
    • Use Restate.key() (Java) or workflowKey() (Kotlin) to access the workflow’s unique ID
  • Additional handlers use the @Shared annotation and can signal or query the workflow. They can run concurrently with the run handler and until the retention time expires.

Configuring services

Check out the service configuration docs to learn how to configure service behavior, including timeouts and retention policies.