How do you build and use an RSocketRequester in Spring — establishing a connection, attaching route metadata, and choosing the interaction model?
answer
- Inject Builder → tcp()/websocket() → requester
- route() encodes routing metadata, no URLs
- terminal chooses model: retrieveMono/retrieveFlux/send
- one connection multiplexes many calls — reuse
- cold publishers: subscribe or nothing sends
basics
~10 sAutowire 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.
solid answer
~40 sSpring Boot autoconfigures an RSocketRequester.Builder. You obtain a live requester by connecting a transport: builder.tcp(host, port) or builder.websocket(uri), optionally configuring the connection setup with setupRoute(), setupData(), setupMetadata(), and a dataMimeType. The returned RSocketRequester is reusable and multiplexes many requests over one connection. Per call you chain route("a.b.{v}", var) — which encodes RSocket routing metadata (the requester has no URLs) — then data(payload), then a terminal method that selects the interaction model: retrieveMono(Type) for request-response, retrieveFlux(Type) for request-stream (or request-channel if data() received a Flux), and send() for fire-and-forget. All returns are cold reactive publishers, so nothing is transmitted until you subscribe. Keep the requester as a singleton bean and reuse the connection rather than reconnecting per request.
code
java · 35 lines@Configuration
class RSocketClientConfig {
@Bean
RSocketRequester requester(RSocketRequester.Builder builder) {
return builder
.setupRoute("client.connect")
.dataMimeType(MediaType.APPLICATION_JSON)
.tcp("localhost", 7000); // reuse this single connection
}
}
@Service
class QuoteClient {
private final RSocketRequester requester;
QuoteClient(RSocketRequester requester) { this.requester = requester; }
Mono<Quote> price(String symbol) { // request-response
return requester.route("price.{symbol}", symbol)
.retrieveMono(Quote.class);
}
Flux<Quote> stream(String symbol) { // request-stream
return requester.route("quotes.{symbol}", symbol)
.retrieveFlux(Quote.class);
}
Mono<Void> report(Metric m) { // fire-and-forget
return requester.route("metrics").data(m).send();
}
Flux<Ack> upload(Flux<Chunk> chunks) { // request-channel
return requester.route("upload").data(chunks)
.retrieveFlux(Ack.class);
}
}go deeper
Show the basic chain: builder.tcp(...), route(), data(), retrieveMono/retrieveFlux/send.
Explain connection reuse/multiplexing, routing metadata vs URLs, and how the terminal method selects the model.
Cover setup frame (setupRoute/Data + @ConnectMapping), custom metadata for auth, cold-publisher laziness, and codec/mime alignment.
Address connection lifecycle, resilience/reconnect strategy, and server-initiated calls via a captured requester.
`RSocketRequester` is Spring's reactive, fluent client for calling an RSocket responder. Understanding it means understanding three phases: **connect**, **describe the request (route + data)**, and **choose the interaction model (terminal call)**. **1. Obtaining a requester.** Spring Boot autoconfigures an `RSocketRequester.Builder` bean (when `spring-boot-starter-rsocket` is present) preloaded with the app's `RSocketStrategies` (encoders/decoders, e.g. Jackson). You do not `new` a requester; you inject the builder and connect a transport: ``` RSocketRequester requester = builder .setupRoute("connect") // route sent on the SETUP frame .setupData(new ClientInfo(...)) // setup payload .dataMimeType(MediaType.APPLICATION_JSON) .tcp("localhost", 7000); // or .websocket(URI) ``` `tcp(...)` / `websocket(...)` return a connected `RSocketRequester`. A single requester wraps one RSocket connection and **multiplexes** any number of concurrent interactions over it, so it should be created once and reused (a singleton bean), not per call. The `setupRoute`/`setupData`/`setupMetadata` configure the one-time SETUP frame that the responder can handle with `@ConnectMapping`. **2. Routing metadata.** RSocket frames carry *metadata* separate from *data*. There are no URLs; Spring routes by attaching **routing metadata** (well-known mime type `message/x.rsocket.routing.v0`, `WellKnownMimeType.MESSAGE_RSOCKET_ROUTING`) via `route(String route, Object... routeVars)`. The route is an Ant-style pattern with `{}` template variables that `routeVars` fill in; on the responder side these match `@MessageMapping` patterns and bind to `@DestinationVariable`. You can attach additional custom metadata (e.g. a bearer token) with `metadata(value, mimeType)` — commonly used for per-request auth alongside Spring Security's RSocket support. **3. Data + terminal (interaction model).** `data(Object)` sets the outgoing payload; it may be a concrete value, a `Mono`, or a `Flux` (a `Flux` implies streaming outbound, i.e. request-channel). The **terminal** method both triggers the call and fixes the interaction model: - `retrieveMono(Class<T> | ParameterizedTypeReference<T>)` → **request-response**. - `retrieveFlux(...)` → **request-stream**, or **request-channel** when `data(...)` was a `Flux`. - `send()` → **fire-and-forget**; returns `Mono<Void>` completing when the frame is written (no app reply). - `retrieveMono(Void.class)` can also be used to await a request-response completion where the body is empty. **Laziness & backpressure.** Every returned publisher is **cold**: no bytes go on the wire until something subscribes. Streaming calls honor Reactive Streams demand over RSocket REQUEST_N frames, so a slow subscriber backpressures the producer end-to-end. **Gotchas.** - Forgetting to subscribe means the call silently never happens. - Reconnecting per request defeats multiplexing and wastes resources — reuse the requester; add resilience with reconnect strategies on the underlying transport if needed. - `dataMimeType` and codecs must be compatible on both ends; a mismatch surfaces as encoding/decoding errors. - `route()` variables are positional and expand the template — extra/missing vars cause errors. - Blocking (`.block()`) inside a reactive pipeline can starve the event loop; keep it reactive. **When to use.** Use `RSocketRequester` for any Spring-side client of an RSocket responder — service-to-service reactive RPC, streaming subscriptions, or bidirectional channels. For server-initiated calls back to a connected client, you can capture the client's `RSocketRequester` in an `@ConnectMapping`/`@MessageMapping` handler and call it later.
- Why should the RSocketRequester be a singleton rather than created per request?One requester wraps one RSocket connection that multiplexes many concurrent interactions. Reconnecting per request wastes handshakes/resources and loses multiplexing; reuse the connected requester as a bean.
- How would you attach a per-request auth token to a call?Chain .metadata(token, mimeType) (e.g. the bearer-token mime type) alongside route() before the terminal method; the responder reads it via Spring Security's RSocket metadata support.
saying these in an interview costs you the question
- Instantiating RSocketRequester with new instead of connecting via the injected Builder
- Thinking route() is like an HTTP URL path rather than encoded frame metadata
- Expecting data to be sent without subscribing to the returned publisher
- Opening a new connection for every request