skip to content

RSocket Interaction Models & RSocketRequester

RSocket supports request-response, fire-and-forget, request-stream and request-channel, mapped to Mono and Flux returns with route metadata. Interviewers ask how it differs from HTTP, and the streaming and channel models are the answer.

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

explore

questions

5

What are the four RSocket interaction models, and how does each map to Reactor return types on a Spring @MessageMapping handler?

level: juniorimportance: must knowfreq 70%

answer

  1. 1-in-1-out, 1-in-0-out, 1-in-N-out, N-in-N-out
  2. Mono vs Flux return picks single vs stream
  3. Flux parameter = channel
  4. retrieveMono / retrieveFlux / send()
  5. route() = routing metadata, not a URL

basics

~20 s

Request-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 s

RSocket 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
java
@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

for a junior

Name the four models and match Mono→single, Flux→stream, void→fire-and-forget.

for a middle

Explain how the method signature (return type and Flux parameter) selects the model, plus the matching RSocketRequester terminal calls.

for a senior

Discuss backpressure over REQUEST_N frames, cold/lazy publishers not sending until subscribed, and mismatch behavior.

for a principal

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

context

open as a page

How do you build and use an RSocketRequester in Spring — establishing a connection, attaching route metadata, and choosing the interaction model?

level: middleimportance: should knowfreq 55%

basics

~10 s

Autowire the RSocketRequester.Builder, connect with tcp()/websocket() to get an RSocketRequester. Then call route("path"), data(payload), and a terminal like retrieveMono(), retrieveFlux(), or send() — the terminal picks the interaction model.

open as a page

Explain request-channel in Spring RSocket: how the handler is shaped, how backpressure flows both ways, and when to choose it over request-stream.

level: seniorimportance: should knowfreq 35%

basics

~20 s

Request-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.

open as a page

What is @ConnectMapping in Spring RSocket, how does it differ from @MessageMapping, and what are its constraints?

level: seniorimportance: should knowfreq 40%

basics

~20 s

@ConnectMapping handles the RSocket SETUP frame once, when a client connects — good for auth, registration, or reading setup metadata. Unlike @MessageMapping (per-request, any interaction model), it runs once per connection and cannot return data; it returns void/Mono<Void>.

open as a page

As an architect, how do you choose among the four RSocket interaction models, and what delivery/error guarantees does each imply — especially fire-and-forget?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Match the model to the message shape: one reply → request-response; no reply, best-effort → fire-and-forget; server pushes many → request-stream; both sides stream → request-channel. Fire-and-forget gives no app-level acknowledgement or error back, so use it only for tolerable-loss signals.

open as a page