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-http
for Java API + HTTP endpoint (examplebuild.gradle.kts
/pom.xml
)dev.restate:sdk-java-lambda
for Java API + Lambda endpointdev.restate:sdk-kotlin-http
for Kotlin API + HTTP endpoint (examplebuild.gradle.kts
)dev.restate:sdk-kotlin-lambda
for 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
Context
client, have an overload/optional parameter that allows to set additional options, including headers andidempotencyKey
to 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
/@Workflow
name
argument is deprecated, you can now override the name used by Restate for both services and handlers using the@Name
annotation. 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]Handlers
contains 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.client
todev.restate.client
- Renamed
dev.restate.sdk.client.CallRequestOptions
todev.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
Client
implementation 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:
RestateHttpEndpointBuilder
was deprecated, useRestateHttpServer
instead.RestateLambdaEndpointBuilder
was removed, nowBaseRestateLambdaHandler.register
takesEndpoint.Builder
as 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.TimeoutException
instead 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 DurableFuture
s, 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 Serde
s 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.run(new TypeRef<>() {}, () -> split(task));
In Kotlin, defining state keys/durable promise keys is easier than ever:
val COUNT = stateKey<Int>("greet-count")
By default, we still use Jackson for Java, and Kotlinx serialization for Kotlin, but you can completely override these choices by bringing in your serialization/deserialization stack.
For this, and more details on how to customize the Jackson or Kotlinx serialization defaults, check this documentation page: https://docs.restate.dev/develop/java/serialization
Notable changes:
- Renamed
dev.restate.sdk.common.Serde
todev.restate.serde.Serde
JsonSerdes
is deprecated, use theClass
andTypeTag
overloads ofContext
methods instead. Check above how the newSerde
stack works.KtSerdes
/KtDurablePromiseKey
/KtStateKey
are deprecated. To create a state key in Kotlin usestateKey<T>(name)
, for a durable promise keydurablePromiseKey<T>(name)
. Check above how the newSerde
stack works.Serde
interface now usesdev.restate.common.Slice
instead of directly byte arrays/NIOByteBuffer
.
Kotlin Spring Boot integration
In the SDK release 1.2 we introduced an integration with Spring Boot, specifically designed for the Java API. We now introduced a similar integration in Kotlin too, to let you easily build Restate applications using Kotlin and Spring boot. Check out the quickstart: https://docs.restate.dev/get_started/quickstart?sdk=kotlin
Other changes
- SDK Java 2.0 works only with Restate 1.3 onward.
- There is a new API to call/send requests between services, without using the client. Check out the
dev.restate.common.Request
javadocs for more details. protobuf
is now distributed shaded, in order to avoid dependency conflicts.- Package
sdk-serde-protobuf
was removed. - Removed deprecations.