Small Spring Boot (WebFlux) + Kotlin coroutines example, backed by R2DBC/H2 in memory DB. Shows how Kotlin coroutine services, official Spring Data coroutine repositories, and legacy Reactor (Mono) code coexist and interop in same app.
- Kotlin 2.4.10, JVM toolchain 25
- Spring Boot 4.1.0 (WebFlux,
spring-boot-starter-data-r2dbc, H2 console) - kotlinx-coroutines-reactor
- H2 in-memory DB via R2DBC (schema/data loaded from
schema.sql/data.sqlon startup) - JUnit 5 + kotlin-test + coroutines-test for tests
src/main/kotlin/com/example/webfluxexample/
├── WebfluxExampleApplication.kt entry point
├── domain/Entities.kt User, Order, Profile, Notification, UserDashboard, OrderSummary, EnrichedOrder
├── repository/Repositories.kt UserRepository / OrderRepository (CoroutineCrudRepository), LegacyUserRepository (ReactiveCrudRepository)
├── service/
│ ├── DashboardServices.kt ProfileService, OrderQueryService, NotificationService, DashboardService (parallel fan-out with async/coroutineScope)
│ ├── EnrichmentService.kt simulated shipping/logistics call (delay instead of Thread.sleep)
│ ├── LegacyUserService.kt pretend old unconverted Reactor code (returns Mono<User>)
│ └── OrderSummaryService.kt sequential fetch-then-enrich example
└── web/UserController.kt REST endpoints
- Coroutine repositories need no wrapping —
UserRepository/OrderRepositoryextendCoroutineCrudRepositorydirectly. - Bridging legacy Reactor code —
LegacyUserRepository(ReactiveCrudRepository, returnsMono<User>) called from a coroutine controller endpoint via.awaitSingle(), no rewrite needed. - Exposing coroutine code as Mono —
getUserReactivewraps a suspend call withmono { }for callers not yet migrated. - Fan-out / parallel calls —
DashboardServiceruns three independent suspend calls concurrently withasync+coroutineScope, each result kept in a named variable (no positionalTuple3). - Sequential composition —
OrderSummaryServicefetches user, then order, then enriches it: plain suspend calls, no chaining operators.
| Method | Path | Description |
|---|---|---|
| GET | /users/{id} |
Fetch user via legacy Reactor repository (bridged with awaitSingle()) |
| GET | /users/{id}/reactive |
Fetch user via coroutine repository, exposed as Mono<User> |
| GET | /users/{id}/order-summary |
User + first order + enrichment (delivery estimate) |
| GET | /users/{id}/dashboard |
Profile + recent orders + notifications, fetched concurrently |
Seeded data (src/main/resources/data.sql): users user-1 (Ada Lovelace), user-2 (Alan Turing); orders order-1, order-2 (user-1), order-3 (user-2).
./gradlew bootRunApp starts on default port 8080. Example:
curl localhost:8080/users/user-1/dashboardH2 console available (spring-boot-h2console) at /h2-console; DB URL r2dbc:h2:mem:///testdb;DB_CLOSE_DELAY=-1, user sa, no password.
./gradlew testCovers DashboardService, OrderSummaryService, and UserController (src/test/kotlin/...).
./gradlew build