What are the four RSocket interaction models, and how does each map to Reactor return types on a Spring @MessageMapping handler?
answer
- 1-in-1-out, 1-in-0-out, 1-in-N-out, N-in-N-out
- Mono vs Flux return picks single vs stream
- Flux parameter = channel
- retrieveMono / retrieveFlux / send()
- route() = routing metadata, not a URL
basics
~20 sRequest-response (one in, one out), fire-and-forget (one in, no reply), request-stream (one in, many out), request-channel (many in, many out). In Spring: return Mono for the first two, Flux for streams; a Flux parameter means channel.
solid answer
~40 sRSocket defines four interaction models. Request-response: send one message, get one back — a handler returns Mono<T>. Fire-and-forget: send one message, expect nothing — handler returns Mono<Void> or void. Request-stream: send one message, receive a stream — handler returns Flux<T>. Request-channel: bidirectional streams — the handler takes a Flux<T> parameter and returns a Flux<T>. Spring's @MessageMapping infers the model from the method signature: the return type (Mono vs Flux) picks single-vs-stream response, and a Flux argument signals a channel. On the client, RSocketRequester mirrors this: retrieveMono() for request-response, retrieveFlux() for streams, and send() for fire-and-forget. This symmetry means the same route can be driven correctly only if both sides agree on the model.
code
java · 28 lines@Controller
class QuoteController {
// request-response: Mono in, Mono out
@MessageMapping("price.{symbol}")
Mono<Quote> price(@DestinationVariable String symbol) {
return Mono.just(new Quote(symbol, 42.0));
}
// fire-and-forget: no response
@MessageMapping("metrics")
Mono<Void> record(Metric m) {
return metrics.save(m).then();
}
// request-stream: one in, many out
@MessageMapping("quotes.{symbol}")
Flux<Quote> quotes(@DestinationVariable String symbol) {
return Flux.interval(Duration.ofSeconds(1))
.map(i -> new Quote(symbol, 42.0 + i));
}
// request-channel: Flux in, Flux out
@MessageMapping("chat")
Flux<Message> chat(Flux<Message> incoming) {
return incoming.map(Message::echo);
}
}go deeper
Name the four models and match Mono→single, Flux→stream, void→fire-and-forget.
Explain how the method signature (return type and Flux parameter) selects the model, plus the matching RSocketRequester terminal calls.
Discuss backpressure over REQUEST_N frames, cold/lazy publishers not sending until subscribed, and mismatch behavior.
Frame model choice as an API design decision (delivery guarantees, resource cost, contract symmetry between both peers).
RSocket is a binary, message-driven application protocol built on Reactive Streams semantics (backpressure included). Unlike HTTP's single request/response shape, it defines **four interaction models**, and Spring Messaging exposes all four through annotations and the `RSocketRequester` fluent client. **The four models** 1. **Request-Response** — the client sends one payload and receives exactly one payload back. This is the HTTP-like case. Spring handler returns `Mono<T>`; the client calls `retrieveMono(...)`. 2. **Fire-and-Forget** — the client sends one payload and the server sends *no* response at all (not even an acknowledgement of completion at the app level). Useful for high-volume, lossy-tolerant signals (metrics, logs). Spring handler returns `Mono<Void>` or `void`; the client calls `send()` (which itself returns a `Mono<Void>` that completes when the frame is written, not when processed). 3. **Request-Stream** — the client sends one payload and receives a stream of zero-to-many payloads back, with backpressure. Spring handler returns `Flux<T>`; the client calls `retrieveFlux(...)`. 4. **Request-Channel** — a fully bidirectional stream: both sides send a stream of payloads. Spring handler declares a `Flux<T>` (or `Publisher<T>`) **parameter** and returns a `Flux<T>`; the client passes a `Flux` to `.data(...)` and calls `retrieveFlux(...)`. **How Spring picks the model.** With `@MessageMapping`, you never name the interaction model explicitly. Spring's `RSocketMessageHandler` infers it from the method signature: - Return `Flux` ⇒ streaming response; return `Mono`/plain value ⇒ single response; return `Mono<Void>`/`void` ⇒ no response (fire-and-forget). - A streaming **parameter** (`Flux`) ⇒ request-channel. **The client side — `RSocketRequester`.** It is a thin, fluent, reactive client: ``` requester.route("prices.{symbol}", "AAPL") // route metadata .data(request) // payload (may be a Publisher) .retrieveFlux(Quote.class); // chooses interaction model ``` The *terminal* method chooses the model: `retrieveMono` (request-response), `retrieveFlux` (request-stream, or request-channel when `data(...)` was given a `Flux`), and `send()` (fire-and-forget). If you never subscribe to the returned publisher, **nothing is sent** — these are cold, lazy publishers. **Route metadata.** RSocket has no URLs; routing is carried in frame *metadata*. Spring encodes the route via composite metadata using the well-known mime type `message/x.rsocket.routing.v0` (`WellKnownMimeType.MESSAGE_RSOCKET_ROUTING`). `route("a.b.{id}", var)` expands template variables and attaches this metadata; the responder matches it against `@MessageMapping("a.b.{id}")` patterns (Ant-style, with `{}` variables bound via `@DestinationVariable`). **Edge cases & gotchas.** - Fire-and-forget's `send()` completing does **not** mean the server processed (or even received) the message — there is no application-level reply. - Mismatching models breaks silently-ish: calling `retrieveMono` against a handler that streams gives you only the first element; calling `retrieveFlux` on a request-response route yields a single-element flux. - Request-stream and request-channel honor Reactive Streams backpressure over the wire via RSocket REQUEST_N frames — a slow consumer throttles the producer. - `@ConnectMapping` is a *fifth*, connection-level hook (not an interaction model): it handles the setup payload/metadata at connect time and must return `void`/`Mono<Void>` — it cannot reply with data. **When to use which.** Request-response for RPC-style calls; fire-and-forget for cheap one-way signals; request-stream for server push / subscriptions; request-channel for interactive, long-lived bidirectional exchanges (e.g., live collaboration, flow-controlled uploads).
- How does Spring know a handler is request-channel rather than request-stream?By the parameter type: a streaming input parameter (Flux/Publisher of the payload) makes it request-channel. Request-stream takes a single payload and returns Flux.
- Which client method triggers fire-and-forget, and what does its Mono<Void> signal?RSocketRequester...send(). Its Mono<Void> completes when the frame has been written/sent, not when the server received or processed it — there is no app-level reply.
saying these in an interview costs you the question
- Thinking fire-and-forget still returns an acknowledgement or completion signal from the server
- Claiming you specify the interaction model with an annotation attribute rather than by return/parameter type
- Believing RSocket routes are URLs rather than frame metadata