Skip to content
Bluetape4k docs1.11

Coroutine and Flow Extensions

Latest stable Based on Bluetape4k release 1.11.0

Kotlin coroutines provide the primitives, but backend code still repeats scope ownership, Deferred coordination, Flow windowing and parallel mapping, subject-like multicast behavior, and bridges to Java structured concurrency. Repeating those pieces makes cancellation and shutdown behavior inconsistent. bluetape4k-coroutines packages the repository’s shared contracts.

Decision map for the bluetape4k-coroutines manual

Use this module when the required operator or lifecycle abstraction already exists here and its cancellation contract matches the caller. Keep plain coroutineScope, async, and standard Flow operators when they solve the problem directly. Choose a caller-owned scope when work must stop with a request; do not create a long-lived helper scope for request-scoped work.

dependencies {
implementation(platform("io.github.bluetape4k:bluetape4k-dependencies:<version>"))
implementation("io.github.bluetape4k:bluetape4k-coroutines")
}

The module exposes core and virtual-thread API integration. Java structured-concurrency bridges require a compatible JDK implementation at runtime.

DefaultCoroutineScope, IoCoroutineScope, ThreadPoolCoroutineScope, and VirtualThreadCoroutineScope own dispatchers/jobs and must be closed. DeferredValue starts one eager async computation and offers suspending access through await(); its blocking value property is deprecated for coroutine code. Flow extensions cover batching, ranges, races, backpressure policies, parallel transforms, and multicast subjects.

Structured task scopes provide fail-fast, first-success, and supervised policies. The policy determines which failure cancels siblings and what result can be returned.

import io.bluetape4k.coroutines.deferredValueOf
suspend fun loadAnswer(): Int {
val value = deferredValueOf { 21 * 2 }
return try {
value.await()
} finally {
value.close()
}
}

DeferredValue owns a DefaultCoroutineScope. Closing it is important when the caller may abandon the computation.

TaskStart with
Transform one eager async valueDeferredValue, map, flatMap
Wait for or coordinate plain deferred valuessupport.awaitAny, awaitAnyAndCancelOthers, zip
Batch or window a Flowchunked, windowed, bufferUntilChanged
Preserve order while processing asynchronouslyflow.async
Run bounded parallel transformsmapParallel
Multicast state or eventsBehaviorSubject, PublishSubject
Own a closeable scopeDefaultCoroutineScope, IoCoroutineScope, VirtualThreadCoroutineScope
Select structured failure policytaskScope, firstSuccessTaskScope, supervisedTaskScope
RequirementDefault choiceChoose a bluetape4k helper when
Combine two or three suspend calls inside one requestThe caller’s coroutineScope and asyncThe same winner or zip rule appears in several places; use DeferredSupport.
Transform a Flow asynchronously while preserving input ordermap or flow.asyncElement computations should overlap but emission order must remain stable.
Bound a transform where throughput matters more than result ordermapParallelparallelism can be derived from downstream capacity.
Let a component own both a dispatcher and a scopeA framework lifecycle scopeIf you create the dispatcher too, close the CloseableCoroutineScope from that lifecycle.
Select the first replica to finishawaitAnyUse awaitAnyAndCancelOthers when losers must be cancelled. Both select first completion, not first success.
Model a hot stream with terminal completion or errorStateFlow/SharedFlowUse a subject when explicit complete() or emitError() semantics are required.
Bridge a JDK structured-task policycoroutineScope/supervisorScopeUse structured helpers for direct virtual-thread fail-fast or first-success policies.
suspend fun loadDashboard(userId: String): Dashboard = coroutineScope {
val profile = async { profileClient.load(userId) }
val notices = async { noticeClient.load(userId) }
Dashboard(profile.await(), notices.await())
}

Keeping children out of a separate application scope propagates caller cancellation to both requests. Create a closeable scope only when a component truly owns background work, and connect it to the owner’s close().

val ordered = ids.asFlow()
.async { id -> client.load(id) }
.toList() // preserves input order
val throughput = ids.asFlow()
.mapParallel(parallelism = 8) { id -> client.load(id) }
.toList() // completion order may differ

mapParallel(parallelism = 1) takes the plain map path and preserves order. Values above one use flatMapMerge, so output order must not become an API contract.

3. Select the fastest replica and cancel losers

Section titled “3. Select the fastest replica and cancel losers”
suspend fun <T> fastestReplica(
requests: List<Deferred<T>>,
): T = requests.awaitAnyAndCancelOthers()

The winner is the first task to complete. If that task fails or is cancelled, its result is propagated and all losers are cancelled. Use firstSuccessTaskScope when the requirement is the first successful result.

4. Do not lose the first PublishSubject event

Section titled “4. Do not lose the first PublishSubject event”
val subject = PublishSubject<Event>()
coroutineScope {
val collector = launch { subject.collect(::handle) }
subject.awaitCollector()
subject.emit(Event.Started)
subject.complete()
collector.join()
}

PublishSubject does not replay old values, so an event emitted before registration is lost. Use awaitCollector() in tests and adapters where startup order matters. Terminal calls after complete() or emitError() are ignored.

Use structured scopes at the narrowest lifecycle boundary. Rethrow CancellationException before broad exception handling. For parallel Flow work, set concurrency from the downstream service capacity rather than CPU count alone. Prefer await() over blocking bridges. If a type owns a scope, make the owner Closeable and connect it to application shutdown.

The module integrates with bluetape4k-core, virtual-thread dispatchers, Reactor context helpers, Java CompletableFuture/streams, and Kotlin Flow. Reactor-specific helpers stay in the coroutines.reactor package so applications that do not use Reactor can avoid coupling their code to that model.

There is no central property file. Configure dispatcher, parallelism, buffer size, timeout/deadline, and subject capacity at the call or owning component. The benchmark configuration in the module build is for repository measurements and is not an application runtime default.

await() propagates the original computation failure and cancellation. Fail-fast task scopes cancel remaining work after the first failure; first-success scopes fail when every branch fails; supervised scopes retain partial results. Deadline-based joins throw TimeoutException. Blocking access from a constrained coroutine dispatcher can deadlock or starve the pool, which is why DeferredValue.value is deprecated.

  1. Rethrow CancellationException before a broad catch (e: Exception) branch.
  2. Check that request children do not escape into an application scope and lose caller cancellation.
  3. Document the owner and close point of every dispatcher, scope, and channel created directly.
  4. Distinguish a timeout that cancels remote I/O from one that only stops the local wait.
  5. Use supervisorScope when a test intentionally observes child failure without cancelling its body.
SymptomCheck firstResponse
Computation continues after the caller finishesAn unclosed DeferredValue or manually created scopePrefer await() and call close() from the owning lifecycle.
mapParallel changes result orderWhether parallelism is two or greaterUse flow.async or plain map when ordering is contractual.
A remote call remains after timeoutWhether the client maps coroutine cancellation to actual I/O cancellationVerify the client contract and add explicit deadline/idempotency policy.
The first subject event disappearsWhether emit ran before collector registrationUse awaitCollector() or select a replaying subject/StateFlow.
One child failure cancels every siblingWhether fail-fast is the intended policyUse supervisorScope or supervisedTaskScope when partial results are valid.
Threads remain after shutdownA scope such as ThreadPoolCoroutineScope owns its dispatcherConnect the component’s close() to application shutdown.

Observe active jobs, queue/buffer growth, downstream latency, cancellation rate, and timeout count. Close owned scopes and channels during shutdown. A high parallelism value can move the bottleneck into a database or remote service; capacity limits belong at that boundary.

Use runTest for suspending contracts and supervisorScope when a test intentionally observes child failure without cancelling the test body. Representative suites include DeferredSupportTest, StructuredConcurrencyTest, AsyncFlowTest, subject tests, and each Flow operator test.

Terminal window
./gradlew :bluetape4k-coroutines:test --no-configuration-cache

Repository examples use these APIs through higher-level Ktor and Spring modules. For focused experiments, start from StructuredConcurrencyTest or a single Flow operator test and vary failure order, cancellation, timeout, and parallelism.

Custom Flow operators do not remove the need to understand cold versus hot streams, buffering, and cancellation. Subject-like APIs can hide ownership if exposed globally. Virtual-thread bridges depend on the selected JDK implementation and should not be assumed to improve every CPU-bound workload.

These diagrams are loaded directly from README assets published with the 1.11.0 release and pinned to its immutable commit. They describe this manual’s released structure and runtime flows, not later Snapshot changes. Select a preview to open the SVG at the same release commit.

Module Overview diagram

Release README: bluetape4k/coroutines/README.md

Coroutines Class Structure diagram

Release README: bluetape4k/coroutines/README.md

1. Flow Extension Categories diagram

Release README: bluetape4k/coroutines/README.md

DeferredValue Usage Flow diagram

Release README: bluetape4k/coroutines/README.md

2. chunked(n) — Fixed-Size Chunks diagram

Section titled “2. chunked(n) — Fixed-Size Chunks diagram”

2. chunked(n) — Fixed-Size Chunks diagram

Release README: bluetape4k/coroutines/README.md

3. windowed(size, step) — Sliding Window diagram

Section titled “3. windowed(size, step) — Sliding Window diagram”

3. windowed(size, step) — Sliding Window diagram

Release README: bluetape4k/coroutines/README.md

4. sliding(n) / bufferedSliding(n) — One-Step Sliding Window diagram

Section titled “4. sliding(n) / bufferedSliding(n) — One-Step Sliding Window diagram”

4. sliding(n) / bufferedSliding(n) — One-Step Sliding Window diagram

Release README: bluetape4k/coroutines/README.md

5. mapParallel(parallelism) — Parallel Transformation diagram

Section titled “5. mapParallel(parallelism) — Parallel Transformation diagram”

5. mapParallel(parallelism) — Parallel Transformation diagram

Release README: bluetape4k/coroutines/README.md

6. concatMapEager { } — Order-Preserving Eager Parallel Collection diagram

Section titled “6. concatMapEager { } — Order-Preserving Eager Parallel Collection diagram”

6. concatMapEager { } — Order-Preserving Eager Parallel Collection diagram

Release README: bluetape4k/coroutines/README.md

7. bufferingDebounce(timeout) — Debounced Batching diagram

Section titled “7. bufferingDebounce(timeout) — Debounced Batching diagram”

7. bufferingDebounce(timeout) — Debounced Batching diagram

Release README: bluetape4k/coroutines/README.md

8. throttleLeading / throttleTrailing / throttleBoth — Throttle Windows diagram

Section titled “8. throttleLeading / throttleTrailing / throttleBoth — Throttle Windows diagram”

8. throttleLeading / throttleTrailing / throttleBoth — Throttle Windows diagram

Release README: bluetape4k/coroutines/README.md

9. takeUntil(notifier) / skipUntil(notifier) — Gate Control diagram

Section titled “9. takeUntil(notifier) / skipUntil(notifier) — Gate Control diagram”

9. takeUntil(notifier) / skipUntil(notifier) — Gate Control diagram

Release README: bluetape4k/coroutines/README.md

10. merge(flows) — Unordered Merge diagram

Section titled “10. merge(flows) — Unordered Merge diagram”

10. merge(flows) — Unordered Merge diagram

Release README: bluetape4k/coroutines/README.md

11. pairwise() / zipWithNext() — Adjacent Pairs diagram

Section titled “11. pairwise() / zipWithNext() — Adjacent Pairs diagram”

11. pairwise() / zipWithNext() — Adjacent Pairs diagram

Release README: bluetape4k/coroutines/README.md

12. scanWith(initial) { } — Lazy Scan Seed diagram

Section titled “12. scanWith(initial) { } — Lazy Scan Seed diagram”

12. scanWith(initial) { } — Lazy Scan Seed diagram

Release README: bluetape4k/coroutines/README.md

13. AsyncFlow — Ordered Async Results diagram

Section titled “13. AsyncFlow — Ordered Async Results diagram”

13. AsyncFlow — Ordered Async Results diagram

Release README: bluetape4k/coroutines/README.md