Definition
- RSocket – an application-level, binary, message-driven protocol for reactive systems, running over TCP, WebSocket, or Aeron.
- Request-response – one request, one async response (
Mono), the familiar RPC pattern. - Fire-and-forget – one request, no response (
Mono), ideal for best-effort telemetry. - Request-stream – one request, many responses (
Flux), perfect for live feeds. - Request-channel – bidirectional streaming, both sides send and receive continuously on one call.
- Backpressure – Reactive Streams
request(n)carried across the wire so the consumer controls the producer's rate.
Imagine a dedicated private phone line between Rajesh and National Bank of India that supports several conversation styles. Sometimes Rajesh asks a question and waits for one answer (request-response). Sometimes he drops off a note and does not wait (fire-and-forget). Sometimes he subscribes to live stock prices and the bank keeps sending updates (request-stream). And sometimes both sides talk continuously, like a live trading session where orders and fills flow both ways (channel). If Rajesh can only handle ten updates per second, he tells the bank "only send me ten" and the bank obeys: that is backpressure built into the line itself.
Understanding the Concepts
What is RSocket?
RSocket is an application-level, binary, message-driven protocol designed for reactive systems. It runs over a transport such as TCP, WebSocket, or Aeron, and provides four built-in interaction models along with first-class application-level backpressure, connection resumption, keep-alive, and symmetric bidirectional communication.
package com.bank.rsocket;
import io.rsocket.RSocket;
import io.rsocket.core.RSocketConnector;
import io.rsocket.transport.netty.client.TcpClientTransport;
import io.rsocket.util.DefaultPayload;
import reactor.core.publisher.Mono;
public class RSocketClientDemo {
public static void main(String[] args) {
Mono<RSocket> rSocketMono = RSocketConnector.create()
.connect(TcpClientTransport.create("localhost", 7000));
RSocket rSocket = rSocketMono.block();
// Request-response: get Rajesh's account balance
String response = rSocket.requestResponse(
DefaultPayload.create("SAV2001"))
.map(payload -> payload.getDataUtf8())
.block();
System.out.println("Rajesh's balance: " + response);
rSocket.dispose();
}
}
What are the four interaction models?
Each model suits a different communication pattern.
package com.bank.rsocket;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.util.DefaultPayload;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class FourModelsDemo {
// 1. Request-Response: one request, one async response
public Mono<Payload> getBalance(RSocket rSocket) {
return rSocket.requestResponse(DefaultPayload.create("SAV2001"));
}
// 2. Fire-and-Forget: one request, no response
public Mono<Void> logAuditEvent(RSocket rSocket) {
return rSocket.fireAndForget(
DefaultPayload.create("LOGIN:Rajesh:2024-01-15"));
}
// 3. Request-Stream: one request, many responses
public Flux<Payload> subscribePrices(RSocket rSocket) {
return rSocket.requestStream(
DefaultPayload.create("RELIANCE"));
}
// 4. Request-Channel: bidirectional streaming
public Flux<Payload> tradingSession(RSocket rSocket,
Flux<Payload> orders) {
return rSocket.requestChannel(orders);
}
}
Interaction Models Summary: Request-Response : 1 request → 1 response (balance query) Fire-and-Forget : 1 request → 0 responses (audit log) Request-Stream : 1 request → N responses (price feed) Request-Channel : N requests ↔ N responses (trading session)
How does application-level backpressure work?
RSocket carries Reactive Streams request(n) semantics across the wire. The consumer tells the producer exactly how many messages it can handle, and the producer must not exceed that demand.
package com.bank.rsocket;
import io.rsocket.RSocket;
import io.rsocket.util.DefaultPayload;
import reactor.core.publisher.Flux;
public class BackpressureDemo {
public void streamPricesWithBackpressure(RSocket rSocket) {
rSocket.requestStream(DefaultPayload.create("RELIANCE"))
.limitRate(16) // request 16 at a time
.map(payload -> payload.getDataUtf8())
.doOnNext(price -> System.out.println(
"Received price: " + price))
.subscribe();
// The server only sends 16 prices, waits for more demand,
// then sends the next 16, matching Rajesh's phone speed
}
}
How does RSocket compare to WebSockets?
| Feature | WebSocket | RSocket |
|---|---|---|
| Protocol type | Raw bidirectional byte pipe | Structured message protocol |
| Interaction models | None built-in | 4 models (req/resp, stream, etc.) |
| Backpressure | None | request(n) application-level |
| Resumption | None | Built-in session resume |
| Framing | Manual | Built-in binary framing |
| Symmetric | Yes | Yes |
// WebSocket: raw bytes, you must build framing and flow control yourself
// session.sendMessage(new TextMessage("{\"type\":\"price\",\"symbol\":\"RELIANCE\"}"));
// RSocket: structured interaction with backpressure
rSocket.requestStream(DefaultPayload.create("RELIANCE"))
.limitRate(16)
.subscribe(payload -> process(payload));
How does RSocket compare to gRPC?
| Feature | gRPC | RSocket |
|---|---|---|
| Transport | HTTP/2 only | TCP, WebSocket, Aeron |
| Serialization | Protobuf | Any (JSON, CBOR, Protobuf) |
| Backpressure | HTTP/2 byte-window | request(n) message-level |
| Symmetric | No (client initiates) | Yes (either side initiates) |
| Streaming | Unary, server, client, bidi | 4 models including fire-and-forget |
| Resumption | No | Built-in |
| Ecosystem | Very large | Smaller but growing |
// gRPC: client always initiates, HTTP/2 byte-window flow control
// stub.streamPrices(request, responseObserver);
// RSocket: either side initiates, application-level backpressure
rSocket.requestStream(DefaultPayload.create("RELIANCE"))
.limitRate(16)
.subscribe();
What are the advantages of RSocket?
The advantages make RSocket ideal for real-time reactive microservices.
- Streaming and backpressure are first-class, so you avoid building ad-hoc flow control.
- Binary and efficient, with minimal overhead per frame.
- Transport-agnostic: switch from TCP to WebSocket without changing application code.
- Symmetric: the server can call the client just as the client calls the server.
- Session resumption survives transient network drops without losing stream state.
What are the disadvantages of RSocket?
The disadvantages are worth knowing for an honest interview answer.
- Less ubiquitous than HTTP, so proxies, load balancers, and tooling understand it less well.
- Smaller ecosystem and developer familiarity compared to REST or gRPC.
- Debugging a binary protocol needs special tools.
- Browser support requires the WebSocket transport.
What about connection resumption and keep-alive?
RSocket uses keep-alive frames to detect dead peers and session resumption to survive network drops.
package com.bank.rsocket;
import io.rsocket.core.RSocketConnector;
import io.rsocket.core.Resume;
import io.rsocket.transport.netty.client.TcpClientTransport;
import java.time.Duration;
public class ResumableConnection {
public static void main(String[] args) {
RSocketConnector.create()
.resume(new Resume()
.sessionDuration(Duration.ofMinutes(5))
.retry(reactor.util.retry.Retry.backoff(10,
Duration.ofSeconds(1))))
.keepAlive(Duration.ofSeconds(20), Duration.ofSeconds(90))
.connect(TcpClientTransport.create("localhost", 7000))
.subscribe(rSocket ->
System.out.println("Connected with resumption enabled"));
}
}
What should a senior engineer know?
Choose gRPC when you want broad ecosystem support and HTTP/2 infrastructure. Choose RSocket when you need symmetric streaming, resumption, and precise application-level backpressure. At National Bank of India Pankaj used RSocket for the combined market-data and order channel because both sides stream continuously and backpressure prevents a price surge from crashing mobile clients.
- Never use fire-and-forget for money movement because delivery is not guaranteed.
- Always use
limitRateor a bounded subscriber to keep demand realistic on streams. - RSocket fits naturally with Reactor and Spring RSocket, making integration straightforward.
package com.bank.rsocket;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;
import reactor.core.publisher.Flux;
import java.math.BigDecimal;
import java.time.Duration;
@Controller
public class PriceController {
@MessageMapping("prices.stream")
public Flux<BigDecimal> streamPrices(String symbol) {
// Spring RSocket server endpoint
return Flux.interval(Duration.ofMillis(500))
.map(i -> new BigDecimal("2450.00")
.add(BigDecimal.valueOf(Math.random() * 10)))
.doOnNext(price -> System.out.println(
symbol + " price: " + price));
}
}
Visual Overview
RSocket Four Interaction Models: 1. Request-Response: ┌────────┐ request ┌────────┐ │ Client │───────────▶│ Server │ │(Rajesh)│◀───────────│(Bank) │ └────────┘ response └────────┘ 2. Fire-and-Forget: ┌────────┐ request ┌────────┐ │ Client │───────────▶│ Server │ │ │ (no │ │ └────────┘ response) └────────┘ 3. Request-Stream: ┌────────┐ request ┌────────┐ │ Client │───────────▶│ Server │ │ │◀───────────│ │ │ │◀───────────│ prices │ │ │◀───────────│ stream │ └────────┘ N items └────────┘ 4. Request-Channel (Bidirectional): ┌────────┐ orders ──▶ ┌────────┐ │ Client │ │ Server │ │ │◀── fills ── │ │ └────────┘ └────────┘ Backpressure: Client: request(16) ──▶ Server sends 16 Client: request(16) ──▶ Server sends 16 (consumer controls the rate)
Key Takeaway
RSocket is an application-level, binary, message-driven protocol built for reactive systems. It runs over transports like TCP, WebSocket, or Aeron, and it provides four interaction models out of the box. Request-response is one request and one asynchronous answer. Fire-and-forget sends a message with no reply. Request-stream sends one request and receives a stream of many responses. And request-channel is fully bidirectional, streaming requests up and responses down on the same logical stream. Its signature feature is application-level backpressure based on Reactive Streams, so the consumer tells the producer exactly how many messages it can handle and the producer must obey.
At National Bank of India Rajesh used all four models over a single connection for the trading app. A balance check is request-response, an audit login event is fire-and-forget, a live price feed for Reliance is request-stream, and an interactive order session where orders flow up and acknowledgments flow down is request-channel. The backpressure matters in practice: the price feed can produce ticks far faster than a mobile screen can render, so the client requests only a few ticks at a time and the server never floods it.
Compared to WebSockets, RSocket is WebSockets plus semantics: WebSocket is a raw bidirectional byte pipe, while RSocket adds structured interaction models, backpressure, and resumption. Compared to gRPC, both are binary streaming RPC technologies, but gRPC is tied to HTTP/2 with Protobuf and a request-initiated model, whereas RSocket is transport-agnostic, symmetric, and puts backpressure at the application level. Reach for gRPC when you want the broad HTTP/2 ecosystem, and reach for RSocket when you need symmetric streaming, session resumption, and precise application-level flow control.
Remember one sentence: RSocket is a binary reactive protocol with four interaction models and built-in application-level backpressure, where either peer can initiate and streaming is first-class.
---