Explain request-channel in Spring RSocket: how the handler is shaped, how backpressure flows both ways, and when to choose it over request-stream.
answer
- Flux param in + Flux return out = channel
- client: data(flux).retrieveFlux()
- REQUEST_N frames = per-direction backpressure
- inbound Flux is single-subscription
- channel when client keeps sending while receiving
basics
~20 sRequest-channel is a two-way stream. The @MessageMapping handler takes a Flux parameter (inbound stream) and returns a Flux (outbound stream); the client passes a Flux to data() and calls retrieveFlux(). Both directions are independent, backpressured Reactive Streams. Choose it when the client must keep sending while receiving.
solid answer
~50 sRequest-channel is RSocket's fully bidirectional model: many messages in, many messages out, over one logical stream. In Spring the responder handler declares a Flux<In> parameter and returns Flux<Out>; the client calls requester.route(...).data(someFlux).retrieveFlux(Out.class). The inbound and outbound streams are separate Reactive Streams pipelines, each with independent backpressure signalled over RSocket REQUEST_N frames — the consumer of each direction controls demand, so a slow reader throttles the far-side producer without unbounded buffering. Use request-channel (versus request-stream) when the client needs to keep emitting after the initial request: flow-controlled uploads, interactive/bidirectional sessions, live collaboration, or when server output depends continuously on ongoing client input. If the client sends exactly one request and only consumes a stream, request-stream is simpler. Beware: subscribing to the inbound Flux more than once, or never subscribing, breaks the channel; and errors on either leg terminate that leg.
code
java · 14 lines// Responder
@MessageMapping("upload")
Flux<ChunkAck> upload(Flux<Chunk> chunks) {
return chunks
.concatMap(chunk -> store.save(chunk) // honors backpressure per chunk
.thenReturn(new ChunkAck(chunk.index())));
}
// Client
Flux<Chunk> chunks = Flux.fromIterable(file.split());
Flux<ChunkAck> acks = requester.route("upload")
.data(chunks) // outbound stream
.retrieveFlux(ChunkAck.class); // inbound stream
acks.subscribe(ack -> log.info("stored chunk {}", ack.index()));go deeper
Recognize the Flux-in/Flux-out handler shape and the data(flux).retrieveFlux() client call.
Explain the two independent streams and that it's for bidirectional send/receive.
Detail REQUEST_N backpressure per direction, single-subscription inbound Flux, and channel-vs-stream selection.
Weigh flow control, partial-failure/cancellation semantics, and API design tradeoffs of channel vs multiple streams.
**Request-channel** is the richest of RSocket's four interaction models: a single logical stream where **both** peers send a stream of payloads — *N-in, N-out* — with independent backpressure in each direction. It is the natural fit for interactive, long-lived exchanges. **Handler shape on the responder.** With `@MessageMapping`, Spring recognizes request-channel by a **streaming input parameter**: ``` @MessageMapping("telemetry") Flux<Ack> telemetry(Flux<Reading> readings) { return readings .buffer(Duration.ofSeconds(1)) .map(batch -> new Ack(batch.size())); } ``` The `Flux<Reading>` parameter *is* the inbound stream from the client; the returned `Flux<Ack>` is the outbound stream to the client. The two are wired to different halves of the same RSocket stream. Contrast with **request-stream**, whose handler takes a *single* payload and returns a `Flux`. **Client side.** The client supplies a `Flux` as the request data and retrieves a `Flux`: ``` Flux<Ack> acks = requester.route("telemetry") .data(readingFlux) // outbound stream .retrieveFlux(Ack.class); // inbound stream ``` The same terminal method (`retrieveFlux`) serves both request-stream and request-channel; the difference is whether `data(...)` was handed a `Flux`/`Publisher` (channel) or a single value (stream). **Backpressure — both ways.** RSocket implements Reactive Streams demand over the wire using **REQUEST_N** frames. Each direction is an independent flow-controlled pipeline: the *receiver* of a direction issues demand, and the *sender* only emits up to that demand. So if the server consumes client readings slowly, its demand upstream (via REQUEST_N to the client) throttles the client's producer; symmetrically, a slow client consumer throttles the server's `Flux<Ack>`. This end-to-end backpressure avoids unbounded buffering and is a core reason to use RSocket over naive streaming. **Lifecycle & edge cases.** - The inbound `Flux` parameter is a **single-subscription** stream: subscribe to it once (or compose operators onto it). Subscribing twice, or never subscribing while also not returning a derived stream, breaks or stalls the channel. - **Errors** on either leg terminate *that* leg with an onError; Spring surfaces responder errors to the client per RSocket error frames. Design for partial termination (e.g., a failing outbound doesn't automatically drain inbound cleanly unless composed). - **Completion** of the outbound `Flux` sends a completion frame; the inbound completes when the client stops sending. A channel stays open as long as both sides have activity/demand. - Cancellation propagates: cancelling the client's subscription to `acks` cancels the outbound stream via a CANCEL frame. **Request-channel vs request-stream — when to choose.** - **Request-stream**: client makes one request, then only *consumes* a server stream (server push, subscriptions, tailing). Simpler; no inbound Flux. - **Request-channel**: client must keep *sending* while receiving — flow-controlled/large uploads split into chunks with per-chunk acks, interactive bidirectional sessions (chat, collaborative editing), or adaptive streams where server output continuously depends on live client input (e.g., client adjusts a subscription in-flight). If you find yourself opening a second request-stream just to feed input back, a channel is the right model. **Gotchas.** - Treating the inbound `Flux` as replayable/multi-subscriber. - Blocking inside the pipeline, defeating backpressure. - Assuming ordering/coupling between the two directions — they are independent; don't rely on an Ack corresponding positionally to a Reading unless you enforce it. - Overusing channel where request-stream suffices adds complexity for no benefit.
- How does backpressure actually travel across the network in request-channel?Via RSocket REQUEST_N frames: the receiver of each direction signals how many items it can accept, and the sender emits only up to that demand — independently for the inbound and outbound legs.
- When would request-stream be the wrong choice, forcing you to request-channel?When the client must keep emitting after the initial request — chunked uploads with acks, interactive sessions, or server output that continuously depends on ongoing client input. Request-stream has no inbound stream.
saying these in an interview costs you the question
- Claiming request-channel shares one backpressure signal for both directions
- Subscribing to the inbound Flux parameter multiple times
- Assuming the outbound item N corresponds to inbound item N automatically
- Using request-channel when the client only consumes and never sends