Releases: restatedev/sdk-java
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
v2.4.2
What's Changed
- Various improvements to the code generator
New Contributors
- @muhamadazmy made their first contribution in #562
- @squarepegg made their first contribution in #563
Full Changelog: v2.4.1...v2.4.2
v2.4.1
Notable changes
- Fix bug in the state machine that caused, in some cases, the invocation to hang when inactivity timeout kicked in. #556
- bump handlebars dependency from 4.3.1 to 4.5.0 by @djarnis73 in #557
New Contributors
- @djarnis73 made their first contribution in #557
Full Changelog: v2.4.0...v2.4.1
v2.4.0
Invocation retry policy
When used with Restate 1.5, you can now configure the invocation retry policy from the SDK directly. See https://github.com/restatedev/restate/releases/tag/v1.5.0 for more details on the new invocation retry policy configuration.
[Spring integration] Configure service options
A new API was introduced to configure services using the Spring integration:
// Specifiy the bean name of the configuration
@RestateService(configuration = "greeterConfiguration")
public class Greeter {
// Your service
}
// Example configuration class
public class Configuration {
@Bean
public RestateServiceConfigurator greeterConfiguration() {
return configurator -> configurator.inactivityTimeout(Duration.ofMinutes(2));
}
}What's Changed
- [Release] Bump to 2.4.0-SNAPSHOT by @github-actions[bot] in #531
- Bump org.assertj:assertj-core from 3.26.0 to 3.27.4 by @dependabot[bot] in #536
- Retry policy by @slinkydeveloper in #540
- Protocol V6 + random_seed support by @slinkydeveloper in #542
- Test Suite v3.1 by @slinkydeveloper in #545
- Clarify max attempts meaning by @slinkydeveloper in #546
- Add workflow retention setting at service level by @slinkydeveloper in #547
- Add entrypoint to setup a configurator for Spring Restate services. by @slinkydeveloper in #548
- Dependency bumps by @slinkydeveloper in #549
- [Release] Bump to 2.4.0 by @github-actions[bot] in #550
Full Changelog: v2.3.0...v2.4.0
v2.3.0
IMPORTANT For Spring Users: Due to MadeYourReset CVE, we bumped Vert.x and Netty to latest releases. This results in misaligned Netty dependency when used in combination with Spring Parent POM/Gradle dependency management plugin. To fix it, force the correct Netty version:
- If you use Maven with spring parent POM add
<netty.version>4.1.124.Final</netty.version>to your POM - If you use Gradle Spring dependency management plugin add
netty.version = 4.1.124.Finalto thegradle.propertiesfile
Improvements
- Added ability to disable bidirectional streaming. This can be useful in some environments where HTTP/2 streaming doesn't work properly. To disable it using
RestateHttpServer:
var handler = HttpEndpointRequestHandler.fromEndpoint(
Endpoint.builder().bind(new Counter()).build(),
/* disableBidirectionalStreaming */ true
);
RestateHttpServer.listen(handler);To disable it when using the spring boot integration, add restate.sdk.http.disableBidirectionalStreaming=true to your application.properties
- Add
delayto generated workflow clientssubmitmethod - Added few extension methods to Kotlin ingress client
Bug fixes
- Removed double logging of
TerminalException - Removed noisy logging of coroutine cancellation when irrelevant
- Dependency bumps
- Vert.x 4.5.18
- Kotlin 2.2.10
Full Changelog: v2.2.1...v2.3.0
v2.2.1
What's Changed
- [Release] Bump to 2.3.0-SNAPSHOT by @github-actions[bot] in #512
- Few improvements to errors from sdk-shared-core by @slinkydeveloper in #513
- Improve formatting of journal mismatch by @slinkydeveloper in #514
- Fix usage of filer/classloader for resources. by @slinkydeveloper in #517
- Add commandIndex to mismatch exceptions by @slinkydeveloper in #518
- [Release] Bump to 2.2.1 by @github-actions[bot] in #519
Full Changelog: v2.2.0...v2.2.1
Release 2.2.0
- You can now configure the service/handler options when binding the services, e.g. in Kotlin:
endpoint {
bind(MyService()) {
it.journalRetention = 10.days
it.configureHandler("myHandler") {
it.inactivityTimeout = 10.minutes
}
}
}- The following new configuration options are available for services and handlers:
inactivityTimeout,abortTimeout,idempotencyRetention,journalRetention,ingressPrivate,enableLazyState. Please note, these will only work with Restate 1.4 onward.
What's Changed
- [Release] Bump to 2.2.0-SNAPSHOT by @github-actions in #506
- Endpoint manifest V3 by @slinkydeveloper in #508
- Dep updates for security by @slinkydeveloper in #509
- Update coordinates for the new publishing portal by @slinkydeveloper in #510
- [Release] Bump to 2.2.0 by @github-actions in #511
Full Changelog: v2.1.1...v2.2.0
v2.1.1
What's Changed
- [Release] Bump to 2.2.0-SNAPSHOT by @github-actions in #500
- Make KtTypeTag public by @slinkydeveloper in #504
- [Release] Bump to 2.1.1 by @github-actions in #505
Full Changelog: v2.1.0...v2.1.1
v2.1.0
What's Changed
Kotlin now supports JSON Schema
The Kotlin API now supports OOTB generating Json Schemas, using https://github.com/SMILEY4/schema-kenerator. The schemas will be automatically propagated to restate-server, and they will be available in the generated OpenAPI and in the Restate Playground.
Changelog
- [Release] Bump to 2.1.0-SNAPSHOT by @github-actions in #493
- Fix ktdocs for select and awaitAll by @slinkydeveloper in #494
- Generate Json Schemas for Kotlin by @slinkydeveloper in #495
- Fix #497 by @slinkydeveloper in #498
- [Release] Bump to 2.1.0 by @github-actions in #499
Full Changelog: v2.0.0...v2.1.0
2.0.0
Java/Kotlin SDK 2.0
We're pleased to announce the release of Java/Kotlin SDK 2.0, in combination with Restate 1.3.
Check out the announcement blog post for more details about Restate 1.3 and the new SDK features: https://restate.dev/blog/announcing-restate-1.3/
Below are the changes specific to the Java/Kotlin SDK.
What's new and what changed
New meta packages
From now on you don't need to pick individual packages of the SDK to get started, but you can use the new meta packages:
dev.restate:sdk-java-httpfor Java API + HTTP endpoint (examplebuild.gradle.kts/pom.xml)dev.restate:sdk-java-lambdafor Java API + Lambda endpointdev.restate:sdk-kotlin-httpfor Kotlin API + HTTP endpoint (examplebuild.gradle.kts)dev.restate:sdk-kotlin-lambdafor Kotlin API + Lambda endpoint
E.g. for Java (gradle):
annotationProcessor("dev.restate:sdk-api-gen:2.0.0")
// Java API + HTTP endpoint
implementation("dev.restate:sdk-java-http:2.0.0")For kotlin:
// Code generator
ksp("dev.restate:sdk-api-kotlin-gen:2.0.0")
// Kotlin API + HTTP endpoint
implementation("dev.restate:sdk-kotlin-http:2.0.0")You still need to individually import the code generator packages, as before, because those are not required at runtime.
Code generation and sharing code-generated artifacts
We made few changes to the generated code, with the goal of simplifying sharing code generated artifacts with 3rd party consumers of your Restate services.
Among the notable breaking changes:
- All methods in generated clients, both for normal client and
Contextclient, have an overload/optional parameter that allows to set additional options, including headers andidempotencyKeyto use. Removed the old overloads acceptingCallRequestOptions. For example this code:
client.handler("input", CallRequestOptions.DEFAULT.withIdempotency("my key"))becomes
client.handler("input", opts -> opts.idempotencyKey("my key"))- Annotations
@Service/@VirtualObject/@Workflownameargument is deprecated, you can now override the name used by Restate for both services and handlers using the@Nameannotation. This name won't affect anymore the name prefix of the generated classes. - The clients method
.send(Duration)has been removed in favor of overloads/optional parameter to perform one way calls withdelay. - The new class
[your service name]Handlerscontains inside the old class[your service name]Metadata. - All send methods in context clients now return
InvocationHandle.
A new class will now be generated, called [your service name]Handlers, that lets you easily call your service as follows:
// For service to service
GreeterHandlers.greet(request).call(ctx)
GreeterHandlers.greet(request).send(ctx)
// For Client
val restateClient: Client = Client.connect("https://my-restate/")
GreeterHandlers.greet(request).call(restateClient)
GreeterHandlers.greet(request).send(restateClient)This class has few dependencies on some core classes of the SDK, that we commit to maintain ABI stable.
You can publish/share this generated class, and use it in combination with the Client simply importing the module dev.restate:client for Java or dev.restate:client-kotlin for Kotlin.
On top of that, this class works both for Kotlin API users and with Java API users, letting you call from Java to Kotlin and vice versa.
If you don't use the [...]Client class, but you use only the [...]Handlers, we suggest disabling its code generation as follows. For Java (gradle):
tasks {
withType<JavaCompile> {
options.compilerArgs.addAll(
listOf(
"-Adev.restate.codegen.disabledClientGeneration=my.fqcn.ServiceName"
)
)
}
}In Kotlin KSP (gradle):
ksp {
arg("dev.restate.codegen.disabledClientGeneration", "my.fqcn.ServiceName")
}Note: the generated artifacts from the SDK 1.x versions ARE NOT compatible with the generated artifacts from 2.x onward.
From SDK 2.x, we commit to a stable ABI between generated code artifacts and the SDK versions, meaning you'll be able to safely mix code generated artifacts with SDK version. For example, you'll be able to import a code generated artifact using SDK 2.0 in a project using SDK 2.1.
Client changes
We overhauled the client to interact with Restate. Now it lives in a new maven module ad hoc called dev.restate:client, which you can import separately from the rest of the SDK when you need to interact only with Restate from another Java/Kotlin application.
Notable changes:
- Renamed package
dev.restate.sdk.clienttodev.restate.client - Renamed
dev.restate.sdk.client.CallRequestOptionstodev.restate.client.RequestOptions - Now all client methods returns headers and status code, see interface
dev.restate.client.ResponseHead. - It is possible to bootstrap your own
Clientimplementation using the HTTP client of your choice extendingdev.restate.client.base.BaseClient
Endpoint and RestateHttpServer
We now expose a uniform API across Http and Lambda support to build your service endpoint. In Java:
var endpoint = Endpoint
.bind(new Greeter())
// Run Counter on virtual threads
.bind(new Counter(), HandlerRunner.Options.withExecutor(Executors.newVirtualThreadPerTaskExecutor()));
// E.g. bind to server
RestateHttpServer.listen(endpoint);In Kotlin:
val endpoint = endpoint {
bind(Greeter())
// Decorate the coroutine context used by the handler
bind(Counter(), HandlerRunner.Options(Dispatchers.Default + CoroutineName("counter")))
}
// E.g. bind to server
RestateHttpServer.listen(endpoint)Using HttpEndpointRequestHandler.fromEndpoint(Endpoint) it is also possible to get a Vert.x request handler for your endpoint, that can be used to build the HTTP server manually.
Notable changes:
RestateHttpEndpointBuilderwas deprecated, useRestateHttpServerinstead.RestateLambdaEndpointBuilderwas removed, nowBaseRestateLambdaHandler.registertakesEndpoint.Builderas argument.
The DurableFuture interface
We renamed the Awaitable to DurableFuture and reworked few of its methods, to make it easier to interact with asynchronous events and compose them:
- Added
DurableFuture.withTimeout(Duration)to return a future composed with the timeout. This has the same behavior asDurableFuture.await(Duration)but it returns a future that can be later awaited, instead of awaiting the future immediately. DurableFuture.await(Duration)now throwsdev.restate.sdk.common.TimeoutExceptioninstead ofjava.util.concurrent.TimeoutException.- Added
DurableFuture.map(successMapper),DurableFuture.map(successMapper, failureMapper)andDurableFuture.mapFailure(failureMapper), to map futures result once completed.
Check out the respective DurableFuture Javadocs/DurableFuture KotlinDocs for more details.
Deterministic resilient concurrency
As described in the blog post, Restate 1.3 improves support over deterministic concurrency.
In Java we expose the new API Select to await on multiple futures at the same time:
DurableFuture<String> a12 = Select.<String>select()
.or(a1)
.or(a2);In Kotlin you can use the select function, which now returns a DurableFuture itself and can be composed too:
select {
a1.onAwait { it }
a2.onAwait { it }
}ctx.runAsync to compose asynchronous side effects
Both in Java and Kotlin you can now use the ctx.runAsync API in order to execute a side effect, and return a DurableFuture instead than the result immediately. You can then compose this future with other DurableFutures, for example to implement fan-in/fan-out operations.
InvocationHandle for finer control over service-to-service communication
As described in the blog post, Restate 1.3 features new APIs to interact with running invocations.
All these features are encapsulated in the new InvocationHandle(Javadocs/Kotlin docs) API:
val handle = GreeterClient.fromContext(context).send().greet(request)
// Get invocation id
val invocationId = handle.invocationId()
// Cancel invocation
handle.cancel()
// Attach and await result
val result = handle.attach().await()New Serde stack
We overhauled the Serde stack completely. Now you don't need to pass around, neither figure out Serdes anymore, but you just need to focus on what types you need. For example:
// Defining a state key
StateKey<Integer> REMINDER_COUNT = StateKey.of("reminder_count", Integer.TYPE);
// Using ctx.run
String result = ctx.run(String.class, () -> doSideEffect());Or for generic types:
List<SubTask> subTasks = ctx.r...