Skip to content

Commit 4ab18f2

Browse files
committed
Add groovy-reactor and groovy-rxjava adapter modules for async/await
1 parent f0c26dc commit 4ab18f2

19 files changed

Lines changed: 3679 additions & 591 deletions

File tree

build.gradle

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,6 @@ dependencies {
118118
testImplementation "com.thoughtworks.qdox:qdox:${versions.qdox}"
119119
testImplementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson}"
120120
testImplementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${versions.jackson}"
121-
testImplementation "io.reactivex.rxjava3:rxjava:${versions.rxjava3}"
122-
testImplementation "io.projectreactor:reactor-core:${versions.reactor}"
123121

124122
testFixturesImplementation projects.groovyXml
125123
testFixturesImplementation projects.groovyTest

settings.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ def subprojects = [
6666
'groovy-macro',
6767
'groovy-macro-library',
6868
'groovy-nio',
69+
'groovy-reactor',
70+
'groovy-rxjava',
6971
'groovy-servlet',
7072
'groovy-sql',
7173
'groovy-swing',

src/spec/doc/core-async-await.adoc

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ Key capabilities include:
4343
* **`Awaitable.go`, `AsyncChannel`, and `Awaitable.any`** — task spawning and channel-based communication primitives
4444
* **Framework integration** — built-in adapters for `CompletableFuture` and `Future`;
4545
`Flow.Publisher` support via an auto-discovered runtime adapter;
46-
extensible to RxJava, Reactor, and Spring via the adapter registry
46+
drop-in Reactor and RxJava 3 modules (`groovy-reactor`, `groovy-rxjava`) with
47+
`ServiceLoader`-based auto-discovery; extensible to any framework via the adapter registry
4748
4849
On JDK 21+, async methods automatically leverage
4950
{jdk}/java.base/java/lang/Thread.html#ofVirtual()[virtual threads] for optimal scalability.
@@ -133,11 +134,12 @@ The `await` keyword natively understands `CompletableFuture`, `CompletionStage`,
133134
include::../test/AsyncAwaitSpecTest.groovy[tags=motivation_interop,indent=0]
134135
----
135136
136-
For third-party frameworks like RxJava, Reactor, or Spring, the
137-
`AwaitableAdapterRegistry` SPI allows registering custom adapters so that
138-
`await` works transparently with _any_ asynchronous type — a single
139-
`await` keyword is all the application code needs, regardless of the
140-
underlying async library.
137+
For third-party frameworks like RxJava, Reactor, or Spring, Groovy provides
138+
dedicated adapter modules — `groovy-reactor` and `groovy-rxjava` — that are
139+
auto-discovered via `ServiceLoader` when placed on the classpath.
140+
No manual registration is required: simply add the dependency and `await`
141+
works transparently with `Mono`, `Flux`, `Single`, `Maybe`, `Observable`,
142+
and `Flowable`.
141143
142144
=== Virtual Threads: Free Scalability
143145
@@ -798,18 +800,67 @@ include::../test/AsyncAwaitSpecTest.groovy[tags=adapter_registration,indent=0]
798800
799801
=== Framework Integration
800802
801-
With appropriate adapters registered, `await` works transparently with any async type.
802-
Adapter implementations for frameworks such as RxJava 3, Project Reactor, and Spring
803-
should delegate to the framework's native blocking or conversion methods:
803+
Groovy ships dedicated adapter modules for the two most popular JVM reactive frameworks.
804+
Adding either module to the classpath enables zero-configuration `async`/`await` support
805+
for that framework's types — no manual adapter registration is needed.
806+
807+
==== Project Reactor (`groovy-reactor`)
808+
809+
The `groovy-reactor` module bridges Reactor's `Mono` and `Flux` into Groovy's async system:
810+
811+
* `Mono<T>` → `Awaitable<T>` — use `await(mono)` to resolve the single emitted value
812+
* `Flux<T>` → `AsyncStream<T>` — use `for await (item in flux)` to iterate over all values
813+
814+
[source,groovy]
815+
----
816+
// Add to classpath — adapters are auto-discovered via ServiceLoader
817+
// Gradle: implementation "org.apache.groovy:groovy-reactor:${groovyVersion}"
818+
819+
async {
820+
def result = await Mono.just("hello") // single value
821+
for await (item in Flux.range(1, 5)) { use(item) } // multi-value
822+
}
823+
----
824+
825+
For comprehensive documentation including operators, Spring WebFlux integration, and
826+
`Awaitable` combinators, see the <<reactor-userguide,Project Reactor Integration>> guide.
827+
828+
==== RxJava 3 (`groovy-rxjava`)
829+
830+
The `groovy-rxjava` module bridges RxJava 3's types into Groovy's async system:
831+
832+
* `Single<T>` / `Maybe<T>` → `Awaitable<T>` — use `await(single)` or `await(maybe)`
833+
* `Observable<T>` / `Flowable<T>` → `AsyncStream<T>` — use `for await (item in source)`
834+
835+
[source,groovy]
836+
----
837+
// Add to classpath — adapters are auto-discovered via ServiceLoader
838+
// Gradle: implementation "org.apache.groovy:groovy-rxjava:${groovyVersion}"
839+
840+
async {
841+
def result = await Single.just("hello") // single value
842+
for await (item in Observable.range(1, 5)) { use(item) } // multi-value
843+
}
844+
----
845+
846+
For comprehensive documentation including `Maybe`, `Flowable`, back-pressure, and
847+
`Awaitable` combinators, see the <<rxjava-userguide,RxJava 3 Integration>> guide.
848+
849+
==== Custom Adapters for Other Frameworks
850+
851+
For frameworks not covered by the built-in modules, implement the `AwaitableAdapter` interface
852+
and delegate to the framework's native blocking or conversion methods:
804853
805-
* **RxJava 3**: `Single.blockingGet()`, `Observable.blockingIterable()` (for `AsyncStream`)
806-
* **Reactor**: `Mono.block()`, `Flux.toIterable()` (for `AsyncStream`)
807854
* **Spring**: Return `Awaitable` from `@Async` service methods; adapt `ListenableFuture` via `toCompletableFuture()`
855+
* **Vert.x**: Adapt `io.vertx.core.Future` via `toCompletionStage()`
808856
809857
=== Automatic Discovery
810858
811-
Adapters can also be discovered automatically via `java.util.ServiceLoader` by placing a
859+
Adapters are discovered automatically via `java.util.ServiceLoader` by placing a
812860
provider configuration file at `META-INF/services/groovy.concurrent.AwaitableAdapter`.
861+
The `groovy-reactor` and `groovy-rxjava` modules use this mechanism — adding them to the
862+
classpath is sufficient for transparent integration.
863+
813864
SPI-discovered adapters are loaded at class-initialization time and take priority over
814865
the built-in adapters. Runtime-registered adapters (via `register()`) take highest
815866
priority, allowing application code to override any default.
@@ -1356,7 +1407,7 @@ JavaScript, C#, Kotlin, and Swift, for developers familiar with those languages.
13561407
13571408
| **Async iteration**
13581409
| `for await (x in src) { }` +
1359-
_(supports `AsyncStream`, `Flow.Publisher`, `AsyncChannel`, `Iterable`)_
1410+
_(supports `AsyncStream`, `Flow.Publisher`, `AsyncChannel`, `Iterable`, Reactor `Flux`, RxJava `Observable`/`Flowable`)_
13601411
| `for await (x of src) { }`
13611412
| `await foreach (x in src) { }`
13621413
| _(manual via `Flow`, `AsyncChannel`, or `Flow.collect`)_
@@ -1452,7 +1503,8 @@ _(supports `AsyncStream`, `Flow.Publisher`, `AsyncChannel`, `Iterable`)_
14521503
14531504
| **Third-party support**
14541505
| `AwaitableAdapterRegistry` SPI +
1455-
`Awaitable.from()` / `AsyncStream.from()`
1506+
`Awaitable.from()` / `AsyncStream.from()` +
1507+
Built-in modules: `groovy-reactor`, `groovy-rxjava`
14561508
| _(native `thenable` protocol)_
14571509
| Custom awaiters via `GetAwaiter()`
14581510
| coroutine adapters / bridges
@@ -2029,4 +2081,10 @@ For full javadoc, see the API documentation for the `groovy.concurrent` package.
20292081
20302082
| Executor configuration
20312083
| `Awaitable.setExecutor(myExecutor)` / system property `groovy.async.parallelism`
2084+
2085+
| Reactor integration
2086+
| Add `groovy-reactor` to classpath; `await(mono)` / `for await (x in flux)`
2087+
2088+
| RxJava 3 integration
2089+
| Add `groovy-rxjava` to classpath; `await(single)` / `for await (x in observable)`
20322090
|===

0 commit comments

Comments
 (0)