Skip to content

v2.5.0

Choose a tag to compare

@slinkydeveloper slinkydeveloper released this 13 Jan 16:15
· 20 commits to main since this release

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 Context parameter. Instead, use static methods from the Restate class 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

  1. Remove the annotation processor dependency sdk-api-gen

  2. Remove the Context, ObjectContext, SharedObjectContext, WorkflowContext, SharedWorkflowContext parameters from your @Handler annotated 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.

  3. Replace all the usages of ctx. with Restate.. 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);
    }
  4. Replace all the usages of code-generated clients. There are two ways to invoke services:

    Simple proxy (for direct calls):

    The Restate class lets you create proxies to call services directly using service(Class), virtualObject(Class, key) and workflow(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) and workflowHandle(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