{"id":31042821,"url":"https://github.com/thorlauridsen/spring-boot-java-structured-concurrency","last_synced_at":"2026-04-14T19:31:28.526Z","repository":{"id":313531460,"uuid":"1047913774","full_name":"thorlauridsen/spring-boot-java-structured-concurrency","owner":"thorlauridsen","description":"Spring Boot Java multi-project Gradle build sample using Structured Concurrency for remote requests ","archived":false,"fork":false,"pushed_at":"2026-03-21T11:18:41.000Z","size":278,"stargazers_count":2,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-22T02:34:13.629Z","etag":null,"topics":["async","asynchronous","concurrency","docker-compose","gradle","h2-database","java","liquibase","postgres","rest","restful-api","spring","springboot","springdoc","structured-concurrency"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thorlauridsen.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-31T14:14:35.000Z","updated_at":"2026-03-21T11:18:44.000Z","dependencies_parsed_at":"2025-09-06T18:35:28.497Z","dependency_job_id":"86e6cc3b-d3e0-4865-88fe-1a107f5debc3","html_url":"https://github.com/thorlauridsen/spring-boot-java-structured-concurrency","commit_stats":null,"previous_names":["thorlauridsen/spring-boot-java-structured-concurrency"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/thorlauridsen/spring-boot-java-structured-concurrency","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thorlauridsen%2Fspring-boot-java-structured-concurrency","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thorlauridsen%2Fspring-boot-java-structured-concurrency/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thorlauridsen%2Fspring-boot-java-structured-concurrency/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thorlauridsen%2Fspring-boot-java-structured-concurrency/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thorlauridsen","download_url":"https://codeload.github.com/thorlauridsen/spring-boot-java-structured-concurrency/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thorlauridsen%2Fspring-boot-java-structured-concurrency/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31812968,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T18:05:02.291Z","status":"ssl_error","status_checked_at":"2026-04-14T18:05:01.765Z","response_time":153,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["async","asynchronous","concurrency","docker-compose","gradle","h2-database","java","liquibase","postgres","rest","restful-api","spring","springboot","springdoc","structured-concurrency"],"created_at":"2025-09-14T12:11:34.983Z","updated_at":"2026-04-14T19:31:28.520Z","avatar_url":"https://github.com/thorlauridsen.png","language":"Java","readme":"# Spring Boot + Java + Structured Concurrency\n\nThis is a sample project using\n[Spring Boot](https://github.com/spring-projects/spring-boot),\n[Java](https://www.java.com)\nand \n[Structured Concurrency](https://docs.oracle.com/en/java/javase/21/core/structured-concurrency.html)\nto optimize performance when executing multiple requests\nto one or more remote services. This is useful in the case where the\ndata from one request is not required to continue the next request.\n\n## Scenario\n\nImagine you are building a service which needs to present traveling/vacation offers to a client.\nThis could for example include offers such as:\n- Flights\n- Hotels\n- Rental cars\n\nYou find a third-party service provider for each type of offer, but you are forced\nto send 3 requests to fetch all the relevant offers before returning it to the client.\nThe issue here is that with synchronous code, you would have to execute one\nrequest at a time which could result in a slow response time.\n\n## Services\n\n### Provider REST API\nThe **provider** subproject is independently runnable and will spin up a Spring Boot REST API.\nThis service includes the following endpoints:\n- `GET /flights`\n- `GET /hotels`\n- `GET /rentalcars`\n\nEach endpoint, will return the full list of available entities from the database.\nAn artificial delay of 2000 milliseconds has been implemented for each endpoint.\nThe purpose of this is to showcase the performance benefits when\ncorrectly using Structured Concurrency.\n\nThe **provider** subproject implements both the **model** and **persistence** subprojects.\nIt can interact with an in-memory [H2database](https://github.com/h2database/h2database)\nusing [Spring Data JPA](https://docs.spring.io/spring-data/jpa/reference/index.html).\nAdditionally, it uses [Liquibase](https://github.com/liquibase/liquibase)\nfor database changelogs, where dummy data has been added to the database.\n\n### Gateway REST API\nThe **gateway** subproject is independently runnable and will spin up a Spring Boot REST API.\nThis service includes the following endpoints:\n- `GET /travel/details/async`\n- `GET /travel/details/sync`\n\nFor this project, we use Spring Boot and Structured Concurrency\nso we can achieve optimized performance.\nStructured Concurrency is a modern Java feature\nthat allows us to manage multiple concurrent \ntasks in a more organized way.\n\nThe code below from [TravelService](/apps/gateway/src/main/java/com/github/thorlauridsen/service/TravelService.java)\nshowcases how to use Structured Concurrency to execute 3 requests simultaneously.\n\n```java\npublic TravelDetails getAsync() throws InterruptedException {\n    try (val scope = new StructuredTaskScope.ShutdownOnFailure()) {\n\n        val flightsTask = scope.fork(() -\u003e fetchList(\"/flights\", Flight.class));\n        val hotelsTask = scope.fork(() -\u003e fetchList(\"/hotels\", Hotel.class));\n        val carsTask = scope.fork(() -\u003e fetchList(\"/rentalcars\", RentalCar.class));\n\n        scope.join();\n        scope.throwIfFailed(\n                cause -\u003e new IllegalStateException(\"Failed to fetch travel details\", cause)\n        );\n\n        return new TravelDetails(\n                flightsTask.get(),\n                hotelsTask.get(),\n                carsTask.get()\n        );\n    }\n}\n```\n\nThe benefit is that we do not have to wait for one request to finish\nbefore starting the next request. The combined response time will be\napproximately the same duration as the slowest of the three requests.\n\nYou can see an example of how the data is\nfetched synchronously in the code below:\n```java\npublic TravelDetails getSync() {\n    val flights = fetchList(\"/flights\", Flight.class);\n    val hotels = fetchList(\"/hotels\", Hotel.class);\n    val rentalCars = fetchList(\"/rentalcars\", RentalCar.class);\n\n    return new TravelDetails(flights, hotels, rentalCars);\n}\n```\nThis separate function has been added to showcase the differences\nin performance when running synchronous and asynchronous code.\nExample logs can be seen below:\n\n```\n07:06:38.123 [nio-8080-exec-1] : Fetching travel details synchronously\n07:06:38.123 [nio-8080-exec-1] : Executing request HTTP GET /flights\n07:06:40.572 [nio-8080-exec-1] : Executing request HTTP GET /hotels\n07:06:42.593 [nio-8080-exec-1] : Executing request HTTP GET /rentalcars\n07:06:44.611 [nio-8080-exec-1] : Fetched travel details in 6487 ms\n07:06:49.110 [nio-8080-exec-2] : Fetching travel details asynchronously\n07:06:49.123 [     virtual-87] : Executing request HTTP GET /hotels\n07:06:49.123 [     virtual-85] : Executing request HTTP GET /flights\n07:06:49.124 [     virtual-89] : Executing request HTTP GET /rentalcars\n07:06:51.141 [nio-8080-exec-2] : Fetched travel details in 2030 ms\n```\n\nWhen fetching data from **n** independent external services:\n\n#### Total execution time\n- **Synchronous code**: Sum of individual request times `T_sync = t₁ + t₂ + ... + tₙ`\n- **Asynchronous code**: Duration of the slowest request `T_async = max(t₁, t₂, ..., tₙ)`\n\n## Usage\nClone the project to your local machine, go to the root directory and use\nthese two commands in separate terminals.\n```\n./gradlew gateway:bootRun\n```\n```\n./gradlew provider:bootRun\n```\nThe provider service will be running with an in-memory H2 database.\nYou can also use IntelliJ IDEA to easily run the two services at once.\n\n### Docker Compose\nTo run the project with [Docker Compose](https://docs.docker.com/compose/), go to the root directory and use:\n```\ndocker-compose up -d\n```\nThis will run the two services at once where the provider service is using a PostgreSQL database.\n\n### Swagger Documentation\nOnce both services is running, you can navigate to http://localhost:8080/\nand http://localhost:8081/ to view the Swagger documentation for each service.\n\n## Technology\n- [JDK25](https://openjdk.org/projects/jdk/25/) - Latest JDK with long-term support\n- [Gradle](https://github.com/gradle/gradle) - Used for compilation, building, testing and dependency management\n- [Spring Boot Web MVC](https://github.com/spring-projects/spring-boot) - For creating REST APIs\n- [Spring Data JPA](https://docs.spring.io/spring-data/jpa/reference/index.html) - Repository support for JPA\n- [Springdoc](https://github.com/springdoc/springdoc-openapi) - Provides Swagger documentation for REST APIs\n- [PostgreSQL](https://www.postgresql.org/) - Open-source relational database\n- [H2database](https://github.com/h2database/h2database) - Provides an in-memory database for simple local testing\n- [Liquibase](https://github.com/liquibase/liquibase) - Used to manage database schema changelogs\n- [WireMock](https://github.com/wiremock/wiremock) - For mocking HTTP services in tests\n- [Lombok](https://github.com/projectlombok/lombok) - Used to reduce boilerplate code\n- [Testcontainers](https://github.com/testcontainers) - Creates a temporary PostgreSQL database for tests\n\n## Testing\nYou can run the tests for this project using the following command:\n```\n./gradlew test\n```\nPlease note that this project uses\n[Testcontainers](https://github.com/testcontainers)\nto create a temporary PostgreSQL database for tests. This requires\na local Docker instance to be running when executing the tests.\n\n## Gradle best practices for Kotlin\n[docs.gradle.org](https://docs.gradle.org/current/userguide/performance.html) - [kotlinlang.org](https://kotlinlang.org/docs/gradle-best-practices.html)\n\n### Preface\nThis project uses Java but the linked article above is generally meant\nfor Kotlin projects. However, I still think that the recommended best\npractices for Gradle are relevant for a Java project as well.\nThe recommendations can be useful for all sorts of Gradle projects.\n\n### ✅ Use Kotlin DSL\nThis project uses Kotlin DSL instead of the traditional Groovy DSL by\nusing **build.gradle.kts** files instead of **build.gradle** files.\nThis gives us the benefits of strict typing which lets IDEs provide\nbetter support for refactoring and auto-completion.\nIf you want to read more about the benefits of using\nKotlin DSL over Groovy DSL, you can check out\n[gradle-kotlin-dsl-vs-groovy-dsl](https://github.com/thorlauridsen/gradle-kotlin-dsl-vs-groovy-dsl)\n\n### ✅ Use a version catalog\n\nThis project uses a version catalog\n[local.versions.toml](gradle/local.versions.toml)\nwhich allows us to centralize dependency management.\nWe can define versions, libraries, bundles and plugins here.\nThis enables us to use Gradle dependencies consistently across the entire project.\n\nDependencies can then be implemented in a specific **build.gradle.kts** file as such:\n```kotlin\nimplementation(local.spring.boot.starter)\n```\n\nThe Kotlinlang article says to name the version catalog **libs.versions.toml**\nbut for this project it has been named **local.versions.toml**. The reason\nfor this is that we can create a shared common version catalog which can\nbe used across Gradle projects. Imagine that you are working on multiple\nsimilar Gradle projects with different purposes, but each project has some\nspecific dependencies but also some dependencies in common. The dependencies\nthat are common across projects could be placed in the shared version catalog\nwhile specific dependencies are placed in the local version catalog.\n\n### ✅ Use local build cache\n\nThis project uses a local\n[build cache](https://docs.gradle.org/current/userguide/build_cache.html)\nfor Gradle which is a way to increase build performance because it will\nre-use outputs produced by previous builds. It will store build outputs\nlocally and allow subsequent builds to fetch these outputs from the cache\nwhen it knows that the inputs have not changed.\nThis means we can save time building\n\nGradle build cache is disabled by default so it has been enabled for this\nproject by updating the root [gradle.properties](gradle.properties) file:\n```properties\norg.gradle.caching=true\n```\n\nThis is enough to enable the local build cache\nand by default, this will use a directory in the Gradle User Home\nto store build cache artifacts.\n\n### ✅ Use configuration cache\n\nThis project uses\n[Gradle configuration cache](https://docs.gradle.org/current/userguide/configuration_cache.html)\nand this will improve build performance by caching the result of the\nconfiguration phase and reusing this for subsequent builds. This means\nthat Gradle tasks can be executed faster if nothing has been changed\nthat affects the build configuration. If you update a **build.gradle.kts**\nfile, the build configuration has been affected.\n\nThis is not enabled by default, so it is enabled by defining this in\nthe root [gradle.properties](gradle.properties) file:\n```properties\norg.gradle.configuration-cache=true\norg.gradle.configuration-cache.parallel=true\n```\n\n### ✅ Use modularization\n\nThis project uses modularization to create a\n[multi-project Gradle build](https://docs.gradle.org/current/userguide/multi_project_builds.html).\nThe benefit here is that we optimize build performance and structure our\nentire project in a meaningful way. This is more scalable as it is easier\nto grow a large project when you structure the code like this.\n\n```\nroot\n│─ build.gradle.kts\n│─ settings.gradle.kts\n│─ apps\n│   └─ gateway\n│       └─ build.gradle.kts\n│   └─ provider\n│       └─ build.gradle.kts\n│─ modules\n│   ├─ model\n│   │   └─ build.gradle.kts\n│   └─ persistence\n│       └─ build.gradle.kts\n```\n\nThis also allows us to specifically decide which Gradle dependencies will be used\nfor which subproject. Each subproject should only use exactly the dependencies\nthat they need.\n\nSubprojects located under [apps](apps) are runnable, so this means we can\nrun the **gateway** or **provider** project to spin up a Spring Boot REST API.\nWe can add more subprojects under [apps](apps) to create additional\nrunnable microservices.\n\nSubprojects located under [modules](modules) are not independently runnable.\nThe subprojects are used to structure code into various layers. The **model**\nsubproject is the most inner layer and contains domain model classes and this\nsubproject knows nothing about any of the other subprojects. The purpose of\nthe **persistence** subproject is to manage the code responsible for\ninteracting with the database. We can add more non-runnable subprojects\nunder [modules](modules) if necessary. This could for example\nbe a third-party integration.\n\n---\n\n#### Subproject with other subproject as dependency\n\nThe subprojects in this repository may use other subprojects as dependencies.\n\nIn our root [settings.gradle.kts](settings.gradle.kts) we have added:\n```kotlin\nenableFeaturePreview(\"TYPESAFE_PROJECT_ACCESSORS\")\n```\nWhich allows us to add a subproject as a dependency in another subproject:\n\n```kotlin\ndependencies {\n    implementation(projects.model)\n}\n```\n\nThis essentially allows us to define this structure:\n\n```\ngateway   \n└─ model\n\nprovider  \n│─ model  \n└─ persistence\n\npersistence  \n└─ model\n\nmodel has no dependencies\n```\n\n## Meta\n\nThis project has been created with the sample code structure from\n[thorlauridsen/spring-boot-java-sample](https://github.com/thorlauridsen/spring-boot-java-sample)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthorlauridsen%2Fspring-boot-java-structured-concurrency","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthorlauridsen%2Fspring-boot-java-structured-concurrency","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthorlauridsen%2Fspring-boot-java-structured-concurrency/lists"}