{"id":29228384,"url":"https://github.com/restatedev/java-workshop","last_synced_at":"2026-06-20T08:31:48.252Z","repository":{"id":259457973,"uuid":"877220271","full_name":"restatedev/java-workshop","owner":"restatedev","description":null,"archived":false,"fork":false,"pushed_at":"2024-10-30T09:37:34.000Z","size":151,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-11-10T01:05:29.997Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/restatedev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-23T09:44:17.000Z","updated_at":"2024-10-30T09:37:37.000Z","dependencies_parsed_at":"2024-10-25T16:49:50.895Z","dependency_job_id":"45d557bf-6f9d-4def-acb5-dda15bec2035","html_url":"https://github.com/restatedev/java-workshop","commit_stats":null,"previous_names":["restatedev/java-workshop"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/restatedev/java-workshop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/restatedev%2Fjava-workshop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/restatedev%2Fjava-workshop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/restatedev%2Fjava-workshop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/restatedev%2Fjava-workshop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/restatedev","download_url":"https://codeload.github.com/restatedev/java-workshop/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/restatedev%2Fjava-workshop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34563535,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-07-03T10:10:46.970Z","updated_at":"2026-06-20T08:31:48.247Z","avatar_url":"https://github.com/restatedev.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hands-on intro: Building resilient Java applications with Restate\n\nThis guide will walk you through building a Java application with Restate.\nIt will cover all the basic concepts and features of Restate that you will need to build your own applications.\n\nPre-requisites:\n- Java 21\n- [Install Restate Server and CLI](https://docs.restate.dev/develop/local_dev#running-restate-server--cli-locally)\n\nIn this example, we will implement an e-commerce checkout process that let's you buy concert tickets.\nThe application will make sure every ticket only gets sold once.\n\n\n\u003cimg src=\"diagram.png\" alt=\"overview\" width=\"500\"/\u003e\n\nContent:\n1. Why do we need Restate?\n2. How does Restate work?\n3. Running your first application\n4. Persisting results\n5. Resiliency\n6. Observability and debugging with Restate CLI\n7. Awakeables\n8. Service communication\n9. Virtual Objects \u0026 State\n10. Idempotency\n11. Timers \u0026 scheduling\n12. Sagas\n13. Summary\n\n## Why do we need Restate?\n\n## How does Restate work?\n\n## Running your first application\n[Quickstart](https://docs.restate.dev/get_started/quickstart?sdk=java)\n\nFollow the [quickstart](https://docs.restate.dev/get_started/quickstart?sdk=java) to get your local setup.\n\nA Restate application consists of services that communicate via handlers, and are annotated with `@Service`.\nEach handler is a service method annotated with `@Handler` and takes a `RestateContext` as its first argument.\n\nRestate sits in front of your services like a reverse proxy or message broker.\nRestate receives incoming requests and routes them to the appropriate handlers.\n\nIn our example, we have a `CheckoutService` with a `checkout` method that reserves the ticket for the user and handles the payment.\nThis will be the first service we will develop:\n\n1. Rename the `Greeter` to `CheckoutService`\n2. Rename the `greet` method to `checkout` and let it take a `String ticket` as an argument.\n\nRun the application again and invoke the checkout method.\n\n```shell\ncurl localhost:8080/CheckoutService/checkout -H 'content-type: application/json' -d '\"Rolling_Stones_31122024\"'\n```\n\n## Persisting results\n[SDK Docs](https://docs.restate.dev/develop/java/journaling-results)\n\nYou can store the result of a (non-deterministic) operation in the Restate execution log (e.g. HTTP calls, randoms, etc). \nRestate replays the result instead of re-executing the operation on retries.\n\nYou can use `ctx.run` to execute an operation and store the result in the execution log.\n\nLet's do the payment by:\n1. Using `ctx.run` to generate a unique payment identifier that is stable on retries.\n2. Do the payment and use the payment identifier for deduplication. \n\n```java\n  private boolean pay(String paymentId, int amount){\n    // call payment provider\n    System.out.println(\"Doing the payment for id \" + paymentId + \" and amount \" + amount);\n    return true;\n  }\n```\n\nHave a look at the [p1/checkoutService](src/main/java/my/example/p1/CheckoutService.java).\n\n## Resiliency\n[Docs](https://docs.restate.dev/concepts/durable_execution)\n\nRestate retries failed invocations automatically.\nOn each retry, Restate replays the execution log to restore the state of the service.\n\nLet the checkout handler fail after doing the payment:\n```java\nthrow new RuntimeException(\"Something went wrong\");\n```\n\nRun the [p1/checkoutService](src/main/java/my/example/p1/CheckoutService.java).\n\nSend a request:\n\n```shell\ncurl localhost:8080/CheckoutService/checkout -H 'content-type: application/json' -d '\"Rolling_Stones_31122024\"'\n```\n\nand see how the retries take place.\n\n## Observability and debugging with Restate CLI\n[Docs](https://docs.restate.dev/operate/introspection)\n\nThe Restate CLI provides a set of commands to [inspect the state](https://docs.restate.dev/operate/introspection?interface=cli) of your services and invocations.\n\nTo list all invocations:\n```shell\nrestate invocations list\n```\n\nTo have a look at the invocations that are currently in a retry loop, you can execute:\n```shell\nrestate invocations list --status backing-off\n```\n\nTo describe a specific invocation:\n\n```shell\nrestate invocations describe \u003cid\u003e\n```\n\nRestate exposes a [SQL interface](https://docs.restate.dev/operate/introspection) to query the entire state of the system:\n\n```shell\nrestate sql \"query\"\n```\n\nRestate also exports OTEL traces to for example Jaeger. Learn more in the [monitoring docs](https://docs.restate.dev/operate/monitoring/tracing). \n\n## Awakeables \n[SDK Docs](https://docs.restate.dev/develop/java/awakeables)\n\nAwakeables pause an invocation while waiting for another process to complete a task. You can use this pattern to let a handler execute a task somewhere else and retrieve the result. \nThis pattern is also known as the callback (task token) pattern.\nWhen the process crashes while waiting for an awakeable, Restate will resume the process and recover the awakeable.\n\nYou can resolve an awakeable via its ID, either via HTTP or with SDK from within another service.\n\nLet's turn the synchronous payment into an asynchronous payment and create an awakeable to wait for the async response. \n\n```shell\n  private void payAsync(String paymentId, int amount, String durableFutureId){\n    // call payment provider\n    System.out.println(\"Doing the payment for id \" + paymentId +\n            \", amount \" + amount +\n            \" and durableFutureId \" + durableFutureId);\n  }\n```\n\nHave a look at the code in [p2/CheckoutService](src/main/java/my/example/p2/CheckoutService.java).\n\nRe-run the service, send a new checkout request:\n\n```shell\ncurl localhost:8080/CheckoutService/checkout -H 'content-type: application/json' -d '\"Rolling_Stones_31122024\"'\n```\n\nand resolve the awakeable with:\n\n```shell\ncurl localhost:8080/restate/awakeables/prom_1PePOqp/resolve -H 'content-type: application/json' -d 'true'\n```\n\n## Service communication\n[Docs](https://docs.restate.dev/develop/java/service-communication)\n\nThe previous sections mainly talked about Durable Execution, retries, and recovery.\n\nRestate does not only provide resiliency for code execution but also for service communication.\n\nWhen you define Restate services, Restate generates clients for you to call these services. \nThese clients can be used for request-response calls (RPC), one-way calls (message-queue-like), and delayed calls (see later).\n\nRestate makes sure that the request is delivered to the target service, and makes sure the execution runs till completion via Durable Execution.\n\nLet's implement a `TicketService` that provides a method to reserve a ticket.\nCreate an `AppMain` class with a main method which creates a Restate HTTP endpoint with the `TicketService` and the `CheckoutService` binded to it. \nWhen you run this you need to re-register the service:\n```shell\nrestate deployments register http://localhost:9080 --force\n```\n\nWe will call this service from the `CheckoutService` to reserve the ticket before handling the payment.\n\nHave a look at [p3/CheckoutService](src/main/java/my/example/p3/CheckoutService.java) \nand [p3/TicketService](src/main/java/my/example/p3/TicketService.java).\n\n## Virtual Objects \u0026 State\n[Concept of Virtual Objects](https://docs.restate.dev/concepts/services)\n[SDK Docs on K/V state](https://docs.restate.dev/develop/java/state)\n\nRestate has an embedded K/V store that can be used to store application state for your handlers.\n\nServices with access to K/V state are called Virtual Objects. \n\nTheir characteristics are:\n- You address a Virtual Object entity by a key (its unique identifier)\n- K/V state is isolated per entity\n- A single entity can only have a single handler running at a time\n- K/V state lives forever\n- Restate attaches the K/V state to the request when invoking a handler\n\nLet's turn the TicketService into a Virtual Object, keyed by ticket ID.\nWe will store the ticket state in the K/V store and use it to implement a durable state machine that tracks the status of the ticket.\n\nHave a look at [p4/TicketService](src/main/java/my/example/p4/TicketService.java) and [p4/CheckoutService](src/main/java/my/example/p4/CheckoutService.java).\n\nYou can use the CLI to inspect the K/V state of Virtual Objects:\n\n```shell\nrestate kv get \u003cSERVICE\u003e \u003cKEY\u003e\n```\n\n## Idempotency\n[Docs](https://docs.restate.dev/invoke/http#invoke-a-handler-idempotently)\n\nRestate guarantees that calls between Restate services are done exactly-once.\nTo also get this deduplication guarantee for incoming requests, you can use idempotency keys.\n\nAn idempotency key is a unique identifier for a request that is sent by the client.\nThe client can send the same request multiple times, but Restate will only process it once.\n\nYou can add an idempotency key to a request by adding a header:\n\n```shell\ncurl localhost:8080/CheckoutService/checkout -H 'content-type: application/json' \\\n    -H 'idempotency-key: ad5472esg4dsg525dssdfa5loi'  \\\n    -d '\"seat2C\"'\n```\n\n## Timers \u0026 scheduling\n[Docs](https://docs.restate.dev/develop/java/durable-timers)\n\nRestate's Durable Execution mechanism gives your code workflow-like semantics. \nSimilar to workflow orchestrators, Restate also includes timer and scheduling functionality.\n\nYou can sleep, schedule a call for later or set a timeout for an operation.\nRestate will track the timers, and make sure they are executed at the right time.\nIf the service crashes, Restate will resume the execution on another instance once the timer has expired. \nThe execution will be fast-forwarded to the point where the timer was set. \n\nThe delayed call functionality lets you schedule an execution for a later time.\nThis can be used as a delayed task queue, for work parallelization or for cron jobs.\n\nIn the example, we will wait for the payment callback with a timeout of 10 minutes. \nIf the payment does not come in time, we will revert the reservation. \nWe will do that with sagas, have a look at the next section.\n\n## Sagas\n[Blog post](https://restate.dev/blog/graceful-cancellations-how-to-keep-your-application-and-workflow-state-consistent/)\n\nDo we still need sagas if we have Durable Execution?\nNot necessarily, meaning that Restate will always try to drive the execution forward, to completion.\n\nBut there might be some cases in which your business logic demands to end processing and revert operations.\nIn this case, you can let your code throw an exception, catch it, do compensations, and throw a `TerminalException`.\nA `TerminalException` is the only type of exception that will not be retried by Restate.\n\nLet's implement a saga for the checkout process. \nWhen the payment fails or times out, we want to abort the payment and revert the ticket reservation.\nThroughout the code we will compose a list of compensation operations that need to be executed when a timeout or terminal exception occurs.\nIn our catch block, we then run these compensations in reversed order. \n\nHave a look at [p5/CheckoutService](src/main/java/my/example/p5/CheckoutService.java).\n\n# Summary\n\n| What we implemented | What we didn't implement, as Restate handles it for us |\n|-------|-------|\n| ✅ Durably executed functions | ❌ Manual retry logic and partial progress recovery |\n| ✅ Reliable RPC, messaging, webhooks | ❌ Deploy MQ, retry logic, timeouts, race conditions, etc. |\n| ✅ Durable timers and scheduling | ❌ Workflow orchestrators, schedulers, or cron jobs |\n| ✅ Idempotency | ❌ Deduplication logic |\n| ✅ Consistent K/V state | ❌ K/V state store, concurrency guards, inconsistent state, dual write problems |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frestatedev%2Fjava-workshop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frestatedev%2Fjava-workshop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frestatedev%2Fjava-workshop/lists"}