v2.5.0
Release 2.5.0
We're excited to announce Restate Java SDK 2.5.0, featuring a major improvement to the developer experience with a new reflection-based API.
Experimental Reflection-Based API
The new reflection-based API removes the need for the annotation processor, making it significantly simpler to use the Restate SDK:
- No annotation processor required: The SDK now uses reflection to discover and bind your services, eliminating the need for build-time annotation processing and making your build simpler and faster.
- No more Context parameters: Handler methods no longer need to accept a
Contextparameter. Instead, use static methods from theRestateclass to access state, promises, and other Restate functionality. - Cleaner method signatures: Your handler methods now have cleaner signatures that focus on your business logic rather than Restate infrastructure.
The new API is opt-in and can be incrementally adopted in an existing project, meaning the existing API will continue to work as usual.
It is marked as experimental and subject to change in the next releases.
This API is currently available only for Java, but we plan to add an equivalent for Kotlin in future releases.
We're looking for your feedback to evolve it and stabilize it!
How to use it
-
Remove the annotation processor dependency
sdk-api-gen -
Remove the
Context,ObjectContext,SharedObjectContext,WorkflowContext,SharedWorkflowContextparameters from your@Handlerannotated methods. For example:@VirtualObject public class Counter { @Handler public void add(ObjectContext ctx, long request) {} @Shared @Handler public long get(SharedObjectContext ctx) {} }
Becomes:
@VirtualObject public class Counter { @Handler public void add(long request) {} @Shared @Handler public long get() {} }
The same applies for interfaces using Restate annotations.
-
Replace all the usages of
ctx.withRestate.. For example:@Handler public void add(ObjectContext ctx, long value) { long currentValue = ctx.get(TOTAL).orElse(0L); long newValue = currentValue + value; ctx.set(TOTAL, newValue); }
Becomes:
@Handler public void add(long value) { var state = Restate.state(); long currentValue = state.get(TOTAL).orElse(0L); long newValue = currentValue + value; state.set(TOTAL, newValue); }
-
Replace all the usages of code-generated clients. There are two ways to invoke services:
Simple proxy (for direct calls):
The
Restateclass lets you create proxies to call services directly usingservice(Class),virtualObject(Class, key)andworkflow(Class, key):Restate.virtualObject(Counter.class, "my-key").add(1); // Direct method call
Handle-based (for advanced patterns):
For asynchronous handling, request composition, or invocation options (such as idempotency keys), use
serviceHandle(Class),virtualObjectHandle(Class, key)andworkflowHandle(Class, key):// Use call() with method reference to return a DurableFuture you can await asynchronously and/or compose with other futures int count = Restate.virtualObjectHandle(Counter.class, "my-counter") .call(Counter::increment) .await(); // Use send() for one-way invocation without waiting InvocationHandle<Integer> handle = Restate.virtualObjectHandle(Counter.class, "my-counter") .send(Counter::increment); // Add request options such as idempotency key int count = Restate.virtualObjectHandle(Counter.class, "my-counter") .call(Counter::increment, InvocationOptions.idempotencyKey("my-idempotency-key")) .await();
Reference sheet
Context API (2.4.x) |
New Restate API (2.5.0) |
|---|---|
ctx.run(...) |
Restate.run(...) |
ctx.random() |
Restate.random() |
ctx.timer() |
Restate.timer() |
ctx.awakeable() |
Restate.awakeable() |
ctx.get(key) / ctx.set(key, value) |
Restate.state().get(key) / Restate.state().set(key, value) |
ctx.promise(key) |
Restate.promise(key) |
| Code generated clients (Services) | Restate.service(Class) / Restate.serviceHandle(Class) |
| Code generated clients (Virtual Objects) | Restate.virtualObject(Class, key) / Restate.virtualObjectHandle(Class, key) |
| Code generated clients (Workflows) | Restate.workflow(Class, key) / Restate.workflowHandle(Class, key) |
Gradual migration
You can gradually migrate to the new API by disabling the annotation processor for specific classes.
For example, if you have a project with a my.example.Greeter and a my.example.Counter, you can decide to migrate only my.example.Counter to use the new API.
To do so, keep the annotation processor dependency and pass the compiler option dev.restate.codegen.disabledClasses as follows:
Gradle
tasks.withType<JavaCompile> {
val disabledClassesCodegen =
listOf(
// Ignore Counter in the annotation processor
"my.example.Counter")
options.compilerArgs.addAll(
listOf(
"-Adev.restate.codegen.disabledClasses=${disabledClassesCodegen.joinToString(",")}",
))
}Maven
<build>
<plugins>
<!-- Setup annotation processor -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>dev.restate</groupId>
<artifactId>sdk-api-gen</artifactId>
<version>${restate.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<!-- Ignore Counter in the annotation processor -->
<arg>-Adev.restate.codegen.disabledClasses=my.example.Counter</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>Full Changelog: v2.4.2...v2.5.0