{"id":13695064,"url":"https://github.com/hantsy/spring-kotlin-coroutines-sample","last_synced_at":"2025-09-07T03:33:06.709Z","repository":{"id":142092329,"uuid":"181314609","full_name":"hantsy/spring-kotlin-coroutines-sample","owner":"hantsy","description":"Spring Kotlin Coroutines sample","archived":false,"fork":false,"pushed_at":"2024-01-31T08:24:59.000Z","size":140,"stargazers_count":54,"open_issues_count":2,"forks_count":16,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-07-18T12:42:43.328Z","etag":null,"topics":["kotlin-coroutines","spring-boot","spring-data-mongodb-reactive","spring-data-neo4j","spring-data-r2dbc"],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hantsy.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}},"created_at":"2019-04-14T13:40:54.000Z","updated_at":"2024-07-03T08:29:39.000Z","dependencies_parsed_at":"2025-07-18T10:36:48.292Z","dependency_job_id":"4ab0516c-2544-4e45-aa93-3d91c1c7cf2f","html_url":"https://github.com/hantsy/spring-kotlin-coroutines-sample","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hantsy/spring-kotlin-coroutines-sample","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hantsy%2Fspring-kotlin-coroutines-sample","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hantsy%2Fspring-kotlin-coroutines-sample/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hantsy%2Fspring-kotlin-coroutines-sample/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hantsy%2Fspring-kotlin-coroutines-sample/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hantsy","download_url":"https://codeload.github.com/hantsy/spring-kotlin-coroutines-sample/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hantsy%2Fspring-kotlin-coroutines-sample/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273992695,"owners_count":25203790,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-07T02:00:09.463Z","response_time":67,"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":["kotlin-coroutines","spring-boot","spring-data-mongodb-reactive","spring-data-neo4j","spring-data-r2dbc"],"created_at":"2024-08-02T17:02:00.770Z","updated_at":"2025-09-07T03:33:06.596Z","avatar_url":"https://github.com/hantsy.png","language":"Kotlin","funding_links":[],"categories":["Kotlin"],"sub_categories":[],"readme":"# Using Kotlin Coroutines with Spring\nBefore Spring 5.2,  you can experience Kotlin Coroutines by the effort from community, eg. [spring-kotlin-coroutine ](https://github.com/konrad-kaminski/spring-kotlin-coroutine) on Github. There are several features introduced in Spring 5.2,  besides [the functional programming style introduced in Spring MVC](https://github.com/hantsy/spring-webmvc-functional-sample), another attractive feature is that Kotlin Coroutines is finally got official support.\n\nKotlin coroutines provides an alternative approach to  write asynchronous applications with Spring Reactive stack, but coding in an imperative style.\n\nIn this post, I will rewrite [my previous reactive sample](https://github.com/hantsy/spring-reactive-sample) using Kotlin Coroutines with Spring.\n\nGenerate a Spring Boot project using [Spring initializr](https://start.spring.io).   Choose the following options in the Web UI, others accept the default options.\n\n* Language: Kotlin \n* Spring Boot version : 2.2.0.BUILD-SNAPSHOT\n* Dependencies: Web Reactive\n\nExtract the files into your local disk. Open the *pom.xml* file in the project root folder, add some modification to get Kotlin Coroutines work in this project. \n\nAdd kotlin-coroutines related dependencies in the *dependencies* section.\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.jetbrains.kotlinx\u003c/groupId\u003e\n    \u003cartifactId\u003ekotlinx-coroutines-core\u003c/artifactId\u003e\n    \u003cversion\u003e${kotlinx-coroutines.version}\u003c/version\u003e\n\u003c/dependency\u003e\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.jetbrains.kotlinx\u003c/groupId\u003e\n    \u003cartifactId\u003ekotlinx-coroutines-reactor\u003c/artifactId\u003e\n    \u003cversion\u003e${kotlinx-coroutines.version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nDefine  a **kotlin-coroutines.version** in the properties using the latest 1.2.0 version.\n\n```xml\n\u003ckotlinx-coroutines.version\u003e1.2.0\u003c/kotlinx-coroutines.version\u003e\n```\n\nKotlin coroutines 1.2.0 is compatible with Kotlin 1.3.30, define a `kotlin.version` property to override the default value in the parent BOM.\n\n```xml\n\u003ckotlin.version\u003e1.3.30\u003c/kotlin.version\u003e\n```\n\nCurrently Spring Data project is busy in adding Kotlin Coroutines support. At the moment Spring Data R2DBC got basic coroutines support in its `DatabaseClient`.  In this sample, we use Spring Data R2DBC for data operations.\n\nAdd Spring Data R2DBC related dependencies, and use PostgresSQL as the backend database.\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.springframework.data\u003c/groupId\u003e\n    \u003cartifactId\u003espring-data-r2dbc\u003c/artifactId\u003e\n    \u003cversion\u003e${spring-data-r2dbc.version}\u003c/version\u003e\n\u003c/dependency\u003e\n\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.postgresql\u003c/groupId\u003e\n    \u003cartifactId\u003epostgresql\u003c/artifactId\u003e\n\u003c/dependency\u003e\n\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.r2dbc\u003c/groupId\u003e\n    \u003cartifactId\u003er2dbc-postgresql\u003c/artifactId\u003e\n\u003c/dependency\u003e\n```\n\n Declare a `spring-data-r2dbc.version` property to use latest Spring Data R2DBC .\n\n```xml\n \u003cspring-data-r2dbc.version\u003e1.0.0.BUILD-SNAPSHOT\u003c/spring-data-r2dbc.version\u003e\n```\n\nEnables Data R2dbc support by subclassing `AbstractR2dbcConfiguration`.\n\n```java\n@Configuration\n@EnableR2dbcRepositories\nclass DatabaseConfig : AbstractR2dbcConfiguration() {\n\n    override fun connectionFactory(): ConnectionFactory {\n        return PostgresqlConnectionFactory(\n                PostgresqlConnectionConfiguration.builder()\n                        .host(\"localhost\")\n                        .database(\"test\")\n                        .username(\"user\")\n                        .password(\"password\")\n                        .build()\n        )\n    }\n}\n```\n\nCreate a Kotlin data class, annotate it with `@Table`  which indicates it is mapped to the table  `posts`.\n\n```java\n@Table(\"posts\")\ndata class Post(@Id val id: Long? = null,\n                @Column(\"title\") val title: String? = null,\n                @Column(\"content\") val content: String? = null\n)\n```\n\nFollow the [Reactive stack to Kotlin Coroutines translation guide](https://docs.spring.io/spring/docs/5.2.0.M1/spring-framework-reference/languages.html#how-reactive-translates-to-coroutines) provided in Spring reference documentation, we create a repository class for CRUD operations of `Post`. \n\n```java\n@Component\nclass PostRepository(private val client: DatabaseClient) {\n\n    suspend fun count(): Long =\n            client.execute().sql(\"SELECT COUNT(*) FROM posts\")\n                    .asType\u003cLong\u003e().fetch().awaitOne()\n\n    fun findAll(): Flow\u003cPost\u003e =\n            client.select().from(\"posts\").asType\u003cPost\u003e().fetch().flow()\n\n    suspend fun findOne(id: Long): Post? =\n            client.execute()\n                    .sql(\"SELECT * FROM posts WHERE id = \\$1\")\n                    .bind(0, id).asType\u003cPost\u003e()\n                    .fetch()\n                    .awaitOneOrNull()\n\n    suspend fun deleteAll() =\n            client.execute()\n                    .sql(\"DELETE FROM posts\")\n                    .fetch()\n                    .rowsUpdated()\n                    .awaitSingle()\n\n    suspend fun save(post: Post) =\n            client.insert()\n                    .into\u003cPost\u003e()\n                    .table(\"posts\")\n                    .using(post)\n                    .await()\n\n    suspend fun init() {\n        save(Post(title = \"My first post title\", content = \"Content of my first post\"))\n        save(Post(title = \"My second post title\", content = \"Content of my second post\"))\n    }\n}\n```\n\nIt is easy to understand the above codes, for the return type of these functions.\n\n* The `Flux\u003cT\u003e`  is changed to Kotlin Coroutines `Flow` type. \n* The `Mono` type is unboxed to parameterized type, and add a `suspend` modifier to the function. If the return result can be null, add a `?` to the return type, eg. `Post?`.\n* The `awaitXXX` or `flow()`  converts the Reactive APIs to Kotlin Coroutines world.\n\nCreate a Controller class for `Post`, Kotlin Coroutines is also supported in the annotated controllers.\n\n```java\n@RestController\n@RequestMapping(\"/posts\")\nclass PostController(\n        private val postRepository: PostRepository\n) {\n\n    @GetMapping(\"\")\n    fun findAll(): Flow\u003cPost\u003e =\n            postRepository.findAll()\n\n    @GetMapping(\"{id}\")\n    suspend fun findOne(@PathVariable id: Long): Post? =\n            postRepository.findOne(id) ?: throw PostNotFoundException(id)\n\n    @PostMapping(\"\")\n    suspend fun save(@RequestBody post: Post) =\n            postRepository.save(post)\n\n    @GetMapping(\"{id}/comments\")\n    fun findCommentsByPostId(@PathVariable id: Long): Flow\u003cComment\u003e =\n            commentRepository.findByPostId(id)\n\n}\n```\n\nYou can also initialize data in a `CommandLineRunner` bean or listen the `@ApplicationReadyEvent`, use a `runBlocking` to wrap coroutines tasks.\n\n```kotlin\nrunBlocking {\n     val deleted = postRepository.deleteAll()\n     println(\" $deleted posts was cleared.\")\n     postRepository.init()\n}\n```\n\nTo run the application successfully, make sure there is a running PostgreSQL server. I prepared a [docker compose file](https://github.com/hantsy/spring-kotlin-coroutines-sample/docker-compose.yml) to simply run a PostgresSQL server and initialize the database schema in a docker container. \n\n```sh\ndocker-compose up\n```\n\nRun the application now, it should  work well as [the previous Reactive examples](https://github.com/hantsy/spring-reactive-sample).\n\nIn additional to the annotated controllers,  Kotlin Coroutines is also supported in functional RouterFunction DSL using the `coRouter`  to define your routing rules. \n\n```kotlin\n@Configuration\nclass RouterConfiguration {\n\n    @Bean\n    fun routes(postHandler: PostHandler) = coRouter {\n        \"/posts\".nest {\n            GET(\"\", postHandler::all)\n            GET(\"/{id}\", postHandler::get)\n            POST(\"\", postHandler::create)\n            PUT(\"/{id}\", postHandler::update)\n            DELETE(\"/{id}\", postHandler::delete)\n        }\n    }\n}\n```\n\nLike the changes in the controller, the `PostHandler` is written in an imperative style.\n\n```kotlin\n@Component\nclass PostHandler(private val posts: PostRepository) {\n\n    suspend fun all(req: ServerRequest): ServerResponse {\n        return ok().bodyAndAwait(this.posts.findAll())\n    }\n\n    suspend fun create(req: ServerRequest): ServerResponse {\n        val body = req.awaitBody\u003cPost\u003e()\n        val createdPost = this.posts.save(body)\n        return created(URI.create(\"/posts/$createdPost\")).buildAndAwait()\n    }\n\n    suspend fun get(req: ServerRequest): ServerResponse {\n        println(\"path variable::${req.pathVariable(\"id\")}\")\n        val foundPost = this.posts.findOne(req.pathVariable(\"id\").toLong())\n        println(\"found post:$foundPost\")\n        return when {\n            foundPost != null -\u003e ok().bodyAndAwait(foundPost)\n            else -\u003e notFound().buildAndAwait()\n        }\n    }\n\n    suspend fun update(req: ServerRequest): ServerResponse {\n        val foundPost = this.posts.findOne(req.pathVariable(\"id\").toLong())\n        val body = req.awaitBody\u003cPost\u003e()\n        return when {\n            foundPost != null -\u003e {\n                this.posts.update(foundPost.copy(title = body.title, content = body.content))\n                noContent().buildAndAwait()\n            }\n            else -\u003e notFound().buildAndAwait()\n        }\n    }\n\n    suspend fun delete(req: ServerRequest): ServerResponse {\n        val deletedCount = this.posts.deleteById(req.pathVariable(\"id\").toLong())\n        println(\"$deletedCount posts was deleted\")\n        return notFound().buildAndAwait()\n    }\n}\n```\n\nBesides annotated controllers and functional router DSL, Spring `WebClient` also embrace Kotlin Coroutines.\n\n```kotlin\n@RestController\n@RequestMapping(\"/posts\")\nclass PostController(private val client: WebClient) {\n    \n    @GetMapping(\"\")\n    suspend fun findAll() =\n            client.get()\n                    .uri(\"/posts\")\n                    .accept(MediaType.APPLICATION_JSON)\n                    .awaitExchange()\n                    .bodyToFlow\u003cPost\u003e()\n\n\n    @GetMapping(\"/{id}\")\n    suspend fun findOne(@PathVariable id: Long): PostDetails = withDetails(id)\n\n\n    private suspend fun withDetails(id: Long): PostDetails {\n        val post =\n                client.get().uri(\"/posts/$id\")\n                        .accept(APPLICATION_JSON)\n                        .awaitExchange().awaitBody\u003cPost\u003e()\n\n        val count =\n                client.get().uri(\"/posts/$id/comments/count\")\n                        .accept(APPLICATION_JSON)\n                        .awaitExchange().awaitBody\u003cLong\u003e()\n\n        return PostDetails(post, count)\n    }\n\n}\n\n```\n\nIn the `withDetails` method, the post and count call the remote APIs one by one in a sequence.  \n\nIf you want to perform coroutines in parallel,   use `async` context to wrap every calls, and put all tasks in a `coroutineScope` context.  To build the return result in `PostDetails`, use `await` to wait the completion of the remote calls.\n\n```kotlin\nprivate suspend fun withDetails(id: Long): PostDetails = coroutineScope {\n        val post = async {\n            client.get().uri(\"/posts/$id\")\n                    .accept(APPLICATION_JSON)\n                    .awaitExchange().awaitBody\u003cPost\u003e()\n        }\n        val count = async {\n            client.get().uri(\"/posts/$id/comments/count\")\n                    .accept(APPLICATION_JSON)\n                    .awaitExchange().awaitBody\u003cLong\u003e()\n        }\n        PostDetails(post.await(), count.await())\n}\n```\n\nCheck out the [codes](https://github.com/hantsy/spring-kotlin-coroutines-sample) from Github.\n\n\n\n\n\n\n\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhantsy%2Fspring-kotlin-coroutines-sample","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhantsy%2Fspring-kotlin-coroutines-sample","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhantsy%2Fspring-kotlin-coroutines-sample/lists"}