skip to content

Kotlin Coroutines & Flow Endpoints

Kotlin handlers can be suspend functions returning plain values or Flow, with coRouter for functional routes and await* bridging Reactor types. Interviewers in Kotlin shops ask this to see whether you can get reactive benefits without the operator chains.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

How do you write a Spring WebFlux controller handler using Kotlin coroutines, and how do suspend functions and Flow<T> return types map onto the reactive model?

level: juniorimportance: must knowfreq 60%

answer

  1. suspend fun -> Mono, Flow -> Flux
  2. needs kotlinx-coroutines-reactor
  3. Unit -> Mono<Void>, null -> empty Mono
  4. runs on event loop — never block
  5. sequential-looking async, no .block()

basics

~20 s

Mark the handler method suspend fun and return a plain value or a Flow<T> instead of Mono/Flux. Spring adapts a suspend function to a single-value response and a Flow to a streaming (many-value) response automatically.

solid answer

~40 s

In an `@RestController`, WebFlux lets you write handlers as `suspend fun` so you use ordinary sequential Kotlin code instead of chaining `Mono`/`Flux` operators. A suspend handler that returns `T` is adapted to `Mono<T>` (single value, or empty for `Unit`); a handler returning `Flow<T>` is adapted to `Flux<T>` (a stream of many values). Under the hood the framework bridges the coroutine to Reactor, so nothing blocks the event loop as long as you never call blocking code directly. This requires `org.jetbrains.kotlinx:kotlinx-coroutines-reactor` on the classpath. Inside the handler you can call other suspend functions (e.g. a suspending WebClient call or an R2DBC/coroutine repository) directly, and the handler simply suspends while awaiting them — no `.block()`, no callback nesting.

code

kotlin · 19 lines
kotlin
@RestController
@RequestMapping("/api/users")
class UserController(private val service: UserService) {

    // suspend fun -> Mono<UserDto>
    @GetMapping("/{id}")
    suspend fun get(@PathVariable id: Long): UserDto =
        service.load(id) // calls a suspending repo/WebClient, no .block()

    // Flow<T> -> Flux<T>, streamed as Server-Sent Events
    @GetMapping(produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
    fun stream(): Flow<UserDto> = service.streamAll()

    // suspend + Unit -> Mono<Void> (204/empty completion)
    @DeleteMapping("/{id}")
    suspend fun delete(@PathVariable id: Long) {
        service.delete(id)
    }
}

go deeper

for a junior

Know the two mappings: suspend fun -> single value (Mono), Flow -> stream (Flux); and that you write sequential code without .block().

for a middle

Explain Unit/null/nullable mappings, the required kotlinx-coroutines-reactor dependency, and streaming request bodies as Flow.

for a senior

Discuss the CoroutinesUtils/ReactiveAdapterRegistry bridge and the event-loop blocking hazard with the withContext(Dispatchers.IO) remedy.

for a principal

Weigh coroutine handlers vs raw Reactor for team ergonomics, interop cost, and observability; reason about backpressure/cancellation preservation across the Flow->Flux boundary.

**The problem it solves.** Spring WebFlux is a non-blocking, reactive web stack. Its native return types are Project Reactor's `Mono<T>` (0..1 values) and `Flux<T>` (0..N values). Writing everything as chains of `map`/`flatMap`/`zip` operators is powerful but verbose and hard to read. Kotlin coroutines let you write asynchronous code that *looks* sequential (`val user = repo.findById(id)`), and WebFlux has first-class support for them since Spring Framework 5.2. **`suspend fun` handlers.** A `suspend` function is a Kotlin function the compiler can pause ("suspend") at await points and resume later without blocking a thread. In a controller you write: ```kotlin @GetMapping("/users/{id}") suspend fun getUser(@PathVariable id: Long): UserDto = service.load(id) ``` WebFlux detects the `suspend` modifier via reflection and wraps the call in `org.springframework.core.CoroutinesUtils.invokeSuspendingFunction`, which turns the invocation into a `Mono`. Mapping rules: - `suspend fun ...(): T` → **`Mono<T>`** (single value). - `suspend fun ...(): T?` returning `null` → an **empty `Mono`** (typically a 200 with no body, or 404 depending on config). - `suspend fun ...(): Unit` → **`Mono<Void>`** (completes with no body). - `fun ...(): Flow<T>` → **`Flux<T>`** (a stream; you do NOT need `suspend` on a `Flow`-returning function because `Flow` is itself the async carrier). **`Flow<T>` return types.** `Flow<T>` is the coroutines equivalent of a cold, back-pressured stream (the coroutines analogue of `Flux`). Returning it produces a streaming response: ```kotlin @GetMapping("/users", produces = [MediaType.TEXT_EVENT_STREAM_VALUE]) fun streamUsers(): Flow<UserDto> = service.streamAll() // Flow -> Flux ``` Spring converts the `Flow` to `Flux` via `kotlinx-coroutines-reactor`'s `asFlux()` (through the `ReactiveAdapterRegistry`), preserving backpressure and cancellation. **Required dependency.** The whole bridge needs `org.jetbrains.kotlinx:kotlinx-coroutines-reactor` (which brings `kotlinx-coroutines-core`). Without it, `suspend`/`Flow` handlers are not recognized as reactive return types and you get startup/handler-mapping errors. **Request body.** You can accept `@RequestBody` as a deserialized object directly, or as a `Flow<T>` for streaming input: ```kotlin suspend fun create(@RequestBody dto: CreateReq): UserDto suspend fun bulk(@RequestBody items: Flow<Item>) { ... } ``` **Key gotcha — never block.** A suspend handler runs on the Reactor/Netty event-loop threads. Calling blocking APIs (JDBC, `Thread.sleep`, `.block()`, blocking file/HTTP I/O) directly stalls the event loop and destroys throughput. Offload blocking work with `withContext(Dispatchers.IO) { ... }`. **When to use.** Prefer coroutine handlers in a Kotlin WebFlux codebase for readability; use plain `Mono`/`Flux` when interop with existing reactive libraries is cleaner or when the team is not on coroutines. Coroutines and Reactor interoperate freely, so you can mix them.

  • Why must you avoid calling JDBC or Thread.sleep directly inside a suspend handler?
    The handler executes on the non-blocking Reactor event-loop threads. Blocking them stalls all in-flight requests sharing that thread, collapsing throughput. Wrap blocking calls in `withContext(Dispatchers.IO)` so they run on a thread pool meant for blocking work.
  • What happens if a suspend handler returns a nullable type and yields null?
    It is adapted to an empty `Mono`, i.e. a completion with no value. Depending on configuration/content that typically produces a 200 with empty body; you usually throw or return a `ResponseEntity` to signal 404 explicitly.

saying these in an interview costs you the question

  • Claiming you must return Mono/Flux even with coroutines (defeats the point).
  • Thinking Flow needs the suspend modifier on the function (it does not).
  • Believing coroutines make blocking JDBC safe on the event loop.
  • Assuming no extra dependency is needed (kotlinx-coroutines-reactor is required).

context

open as a page

How do awaitBody, awaitSingle, and related await* extensions bridge Reactor Mono/Flux into coroutine code, and what are their empty/multi-value semantics?

level: middleimportance: must knowfreq 45%

basics

~20 s

They are suspending extension functions (from kotlinx-coroutines-reactor) that subscribe to a Mono/Flux and suspend the coroutine until a value arrives, then return it. awaitSingle() expects exactly one value; awaitBody<T>() reads and deserializes a request/response body inside a suspend function.

open as a page

What is the coRouter { } DSL in Spring WebFlux and how do coroutine-based functional handlers differ from the reactive router() DSL?

level: middleimportance: should knowfreq 35%

basics

~20 s

coRouter { } is the coroutine version of the functional routing DSL. Routes map to suspend handler functions that take a ServerRequest and return a ServerResponse directly (not a Mono<ServerResponse>), so you write sequential coroutine code.

open as a page

Under the hood, how does Spring WebFlux adapt a suspend function or Flow<T> handler return value into Reactor Mono/Flux, and what role does kotlinx-coroutines-reactor play?

level: seniorimportance: should knowfreq 35%

basics

~20 s

WebFlux detects the suspend modifier/Flow return via reflection and uses kotlinx-coroutines-reactor to bridge them to Reactor: a suspend invocation is wrapped in a mono { } builder (→ Mono), and a Flow<T> is turned into a Flux<T> via asFlux(), registered through Reactor's ReactiveAdapterRegistry.

open as a page

In a coroutine-based WebFlux endpoint, how are threading, cancellation, and blocking calls handled, and what must you get right to keep the event loop healthy?

level: principalimportance: should knowfreq 25%

basics

~20 s

Suspend handlers run on the non-blocking Reactor event-loop threads, so never call blocking code directly — offload it with withContext(Dispatchers.IO). Client disconnects/timeouts cancel the Reactor subscription, which cancels the coroutine (a CancellationException); write cancellation-cooperative code.

open as a page