Overview
The Restate Java/Kotlin SDK is open source and can be found on GitHub: (sdk-java repo).
The Restate SDK lets you implement handlers. Handlers can either be part of a Service, a Virtual Object, or a Workflow. Let's have a look at how to define them.
Services
Services and their handlers are defined as follows:
- Java
- Kotlin
@Servicepublic class MyService {@Handlerpublic String myHandler(Context ctx, String input) {return "my-output";}public static void main(String[] args) {RestateHttpEndpointBuilder.builder().bind(new MyService()).buildAndListen();}}
- Use the
@Service
and@Handler
annotations - Handlers have the
Context
parameter (JavaDocs) as the first parameter. Within the handler, you use theContext
to interact with Restate. The SDK stores the actions you do on the context in the Restate journal to make them durable. - The input parameter (at most one) and return type are optional and can be of any type, as long as they are serializable/deserializable using Jackson Databind (see serialization docs).
- The service will be reachable under the simple class name
MyService
. You can override it by using the annotation fieldname
. - Create an endpoint and bind the service(s) to the Restate endpoint. Listen on the specified port (default
9080
) for connections and requests.
@Serviceclass MyService {@Handlersuspend fun myHandler(ctx: Context, input: String): String {return "my-output"}}fun main() {RestateHttpEndpointBuilder.builder().bind(MyService()).buildAndListen()}
- Use the
@Service
and@Handler
annotations - Handlers have the
Context
parameter (JavaDocs) as the first parameter. Within the handler, you use theContext
to interact with Restate. The SDK stores the actions you do on the context in the Restate journal to make them durable. - The input parameter (at most one) and return type are optional and can be of any type, as long as they are serializable/deserializable using Kotlin serialization (see serialization docs).
- The service will be reachable under the simple class name
MyService
. You can override it by using the annotation fieldname
. - Create an endpoint and bind the service(s) to the Restate endpoint. Listen on the specified port (default
9080
) for connections and requests.
Virtual Objects
Virtual Objects and their handlers are defined similarly to services, with the following differences:
- Java
- Kotlin
@VirtualObjectpublic class MyVirtualObject {@Handlerpublic String myHandler(ObjectContext ctx, String input) {return "my-output";}@Sharedpublic String myConcurrentHandler(SharedObjectContext ctx, String input) {return "my-output";}public static void main(String[] args) {RestateHttpEndpointBuilder.builder().bind(new MyVirtualObject()).buildAndListen();}}
- Use the
@VirtualObject
annotation. - The first argument of the handler must be the
ObjectContext
parameter (JavaDocs). Handlers with theObjectContext
parameter can write to the K/V state store. Only one handler can be active at a time, to ensure consistency. - If you want to have a handler that executes concurrently to the others and doesn't have write access to the K/V state, use the
@Shared
annotation and theSharedObjectContext
. For example, you can use these handlers to read K/V state, or interact with the blocking handler.
@VirtualObjectclass MyVirtualObject {@Handlersuspend fun myHandler(ctx: ObjectContext, input: String): String {return "my-output"}@Sharedsuspend fun myConcurrentHandler(ctx: SharedObjectContext, input: String): String {return "my-output"}}fun main() {RestateHttpEndpointBuilder.builder().bind(MyVirtualObject()).buildAndListen()}
- Use the
@VirtualObject
annotation. - The first argument of the handler must be the
ObjectContext
parameter (JavaDocs). Handlers with theObjectContext
parameter can write to the K/V state store. Only one handler can be active at a time, to ensure consistency. - If you want to have a handler that executes concurrently to the others and doesn't have write access to the K/V state, use the
@Shared
annotation and theSharedObjectContext
. For example, you can use these handlers to read K/V state, or interact with the blocking handler.
Workflows
Workflows are a special type of Virtual Objects, their definition is similar but with the following differences:
- Java
- Kotlin
@Workflowpublic class MyWorkflow {@Workflowpublic String run(WorkflowContext ctx, String input) {// implement workflow logic herereturn "success";}@Sharedpublic void interactWithWorkflow(SharedWorkflowContext ctx, String input) {// implement interaction logic here}public static void main(String[] args) {RestateHttpEndpointBuilder.builder().bind(new MyWorkflow()).buildAndListen();}}
- Create the workflow
- Every workflow implementation needs to have a handler called
run
that implements the workflow logic. This handler uses theWorkflowContext
to interact with the SDK. Therun
handler executes exactly one time per workflow execution/object. - The other handlers of the workflow are used to interact with the workflow: either query it, or signal it. They use the
SharedWorkflowContext
to interact with the SDK. These handlers can run concurrently with therun
handler and can still be called after therun
handler has finished. - Have a look at the workflow docs to learn more.
@Workflowclass MyWorkflow {@Workflowsuspend fun run(ctx: WorkflowContext, input: String): String {// implement workflow logic herereturn "success"}@Handlersuspend fun interactWithWorkflow(ctx: SharedWorkflowContext, input: String) {// implement interaction logic here}}fun main() {RestateHttpEndpointBuilder.builder().bind(MyWorkflow()).buildAndListen()}
- Create the workflow
- Every workflow implementation needs to have a handler called
run
that implements the workflow logic. This handler uses theWorkflowContext
to interact with the SDK. Therun
handler executes exactly one time per workflow execution/object. - The other handlers of the workflow are used to interact with the workflow: either query it, or signal it. They use the
SharedWorkflowContext
to interact with the SDK. These handlers can run concurrently with therun
handler and can still be called after therun
handler has finished. - Have a look at the workflow docs to learn more.
Now that you have a high-level idea of what a Restate service might look like, let's have a look at what the Restate Context allows you to do.
The Java SDK generates code for service clients when you compile your project.
Turn on IntelliJ IDEA annotation processing support, to be able to re-run code generation by pressing CTRL + F9
.
Annotating interfaces
Annotations can also be placed on interfaces. This is useful, for example, if you want to split your service in two packages, one containing the interface and the generated clients, and one containing the implementation.
Manual project setup
You can use the build tool of your choice with the Java/Kotlin SDK. The following instructions use Gradle (Kotlin script).
- Java
- Kotlin
To set up your Java project, run:
gradle init --type java-application
Add the following dependencies:
annotationProcessor("dev.restate:sdk-api-gen:1.1.1")implementation("dev.restate:sdk-api:1.1.1")
When serializing composite types/POJOs with Jackson Databind (default), add the following dependency:
implementation("dev.restate:sdk-serde-jackson:1.1.1")
To set up your Kotlin project, run:
gradle init --type kotlin-application
Add the Kotlin symbol processing and the Kotlin serialization plugin:
plugins {kotlin("plugin.serialization") version "1.9.22"id("com.google.devtools.ksp") version "1.9.22-1.0.18"}
Add the runtime dependency sdk-api-kotlin
and the ksp
dependency sdk-api-kotlin-gen
:
ksp("dev.restate:sdk-api-kotlin-gen:1.1.1")implementation("dev.restate:sdk-api-kotlin:1.1.1")
Depending on the deployment target, add one of the following dependencies:
- To run as HTTP endpoint:
dev.restate:sdk-http-vertx:1.1.1
- To run on AWS Lambda:
dev.restate:sdk-lambda:1.1.1
Manual service definition without annotation processing
In case you don't want to use annotation processing, you can manually define your service by using the classdev.restate.sdk.common.syscalls.ServiceDefinition
. Check the respective JavaDocs/KTDocs for more details.