A durable countdown: register a function, invoke it from the CLI, and watch it tick down — surviving restarts along the way. This is the Java side of the cross-language quickstart.
resonate-sdk-javais pre-release (v0.1.x). This example pinsio.resonatehq:resonate-sdk-java:0.1.1, the first published release on Maven Central. Expect API changes beforev1.0.
A countdown as a loop — plain Java, a for that prints each tick and sleeps between them. What makes it durable is that the loop can run for minutes, hours, or days, and resume exactly where it left off if the worker process dies and comes back.
- Registering a Java method as a named, durable workflow with
r.register. - Persisting each step with
ctx.runand suspending between ticks withctx.sleep— both durable across crashes. - Invoking a registered function by name from the Resonate CLI with
resonate invoke.
These primitives are the foundation every Resonate program is built on, from a simple countdown to distributed sagas and durable sleep.
// The two CLI --arg values arrive as positional arguments:
// `--arg 5 --arg 1` binds to (count=5, delaySeconds=1).
public static String countdown(Context ctx, int count, int delaySeconds) {
for (int i = count; i > 0; i--) {
ctx.run(Quickstart::tick, i).await(); // run a step, persist its result
ctx.sleep(Duration.ofSeconds(delaySeconds)).await(); // durable sleep
}
return "done";
}
public static String tick(Context ctx, int i) {
System.out.println("Countdown: " + i);
return "ok";
}
public static void main(String[] args) throws InterruptedException {
Resonate r = Resonate.builder().url("http://localhost:8001").build();
r.register(Quickstart::countdown);
r.register(Quickstart::tick);
// ... keep the worker alive to receive CLI-invoked work
new CountDownLatch(1).await();
}Two things worth knowing:
- The workflow takes two
intparameters. The CLI passes each--argas one positional argument, so--arg 5 --arg 1becomescount=5, delaySeconds=1. tickis wrapped inctx.runso its result is recorded in a durable promise. On resume after a crash, already-printed ticks short-circuit instead of re-printing.
Every iteration of the loop creates two durable promises — one for the ctx.run step, one for the ctx.sleep. While the countdown is running, inspect them with resonate tree:
resonate tree countdown.1countdown.1
├── countdown.1.1 🟢 (run)
├── countdown.1.2 🟢 (sleep)
├── countdown.1.3 🟢 (run)
└── countdown.1.4 🟡 (sleep)
countdown.1 is the root invocation. The green promises are settled steps; the yellow one is a sleep still in progress. Kill the worker now and the green promises stay resolved — Resonate never re-runs work it has already recorded, so the countdown resumes from the pending sleep rather than starting over.
- Java 21+ — the SDK uses virtual threads, a Java 21 feature. Java 8/11/17 will not work.
- The Resonate CLI (it ships the dev server and the
invokecommand):
brew install resonatehq/tap/resonatePrefer a prebuilt binary? Download one from the releases page.
git clone https://github.com/resonatehq-examples/example-quickstart-java.git
cd example-quickstart-javaStart the server:
resonate devStart the worker (it registers countdown and waits for work):
./gradlew runInvoke the function with execution id countdown.1:
resonate invoke countdown.1 --func countdown --arg 5 --arg 1Expected worker output:
Countdown: 5
Countdown: 4
Countdown: 3
Countdown: 2
Countdown: 1
Once the countdown is working, here are natural next steps in rough order of complexity:
- Crash resilience — invoke with a long delay (
--arg 5 --arg 60), kill the worker mid-countdown with Ctrl-C, then restart it. Resonate resumes the surviving promise where it stopped rather than starting over. - Durable sleep — the
ctx.sleephere can suspend for hours, days, or weeks. - Recursive workflows — a factorial example that fans out sub-invocations. See
example-recursive-factorial-java. - How Resonate works — https://docs.resonatehq.io/evaluate/how-it-works
- Java SDK docs — https://docs.resonatehq.io/develop/java
example-quickstart-java/
├── src/main/java/io/resonatehq/examples/quickstart/Quickstart.java
├── build.gradle.kts dependencies + Java 21 toolchain + main class
├── settings.gradle.kts project name
├── gradlew, gradle/ Gradle wrapper
├── LICENSE Apache-2.0
└── README.md
- Discord: https://resonatehq.io/discord
- X: https://x.com/resonatehqio
- LinkedIn: https://www.linkedin.com/company/resonatehqio
- YouTube: https://www.youtube.com/@resonatehqio
- Journal: https://journal.resonatehq.io