1
0
Files
spring-messaging-demo/protocol-comparison/docs/01-three-protocols.md

3.6 KiB

1. The same use case, three ways

Next: 2. How they measure


One application in this module serves the same two operations over gRPC, RSocket and a raw WebSocket: fetch one quote, and stream N quotes. Same JVM, same heap, same QuoteSource, same five fields.

That constraint is the point. Cross-protocol benchmarks published on the internet almost always compare three different applications, and end up measuring three different serialisation libraries, three JIT states and three thread pools.

What each one is, in one sentence

gRPC RSocket Raw WebSocket
Transport HTTP/2 TCP, WebSocket, or others HTTP/1.1 Upgrade
Schema .proto, mandatory, code-generated none required none
Encoding here protobuf CBOR (Spring's default) JSON
Interaction models 4, declared in the .proto 4, chosen by return type whatever you invent
Demand signalling none in the API request(n) on the wire none at all
Deadlines per call, absolute, propagating none built in none
Browser client needs grpc-web + a proxy yes, over the WebSocket transport yes, natively
Boot 4 starters spring-boot-starter-grpc-server / -client spring-boot-starter-rsocket spring-boot-starter-websocket

The three server sides, side by side

gRPC (GrpcQuoteService) is generated-class inheritance. There is no registration code: the generated ImplBase is a BindableService and Boot registers every such bean.

@Override
public void getQuote(QuoteRequest request, StreamObserver<Quote> observer) {
    observer.onNext(toProto(source.at(request.getSymbol(), 0)));
    observer.onCompleted();
}

RSocket (RSocketQuoteController) is annotation routing, and the interaction model is the return type:

@MessageMapping("quote")
public Mono<Quote> quote(String symbol) { ... }        // request/response

@MessageMapping("quotes")
public Flux<Quote> quotes(StreamSpec spec) { ... }     // request/stream

Raw WebSocket (WebSocketQuoteHandler) is a switch on a string, because there is nothing else:

switch (parts[0]) {
    case "QUOTE"  -> send(session, source.at(parts[1], 0));
    case "STREAM" -> { for (int i = 0; i < count; i++) { send(...); } }
    ...
}

That third block is the honest summary of what a raw WebSocket gives you: a bidirectional pipe for text or bytes. Everything above it — routing, correlation, errors, versioning, demand — is yours to invent, and inventing it is how teams end up with a private, undocumented protocol that only its authors can debug. STOMP exists to stop that, and is covered in ../sse-websocket.

Boot 4 version notes

Boot 4.1.1's BOM pins grpc-java 1.83.1 and protobuf-java 4.35.1, both deliberately older than the newest releases on Maven Central. Overriding them independently is how you get a NoSuchMethodError between grpc-java and its shaded Netty. Note that these have moved since Boot 4.1.0, which pinned 1.80.0 and 4.34.2 — so a spring.grpc.* guide written against 4.1.0 is already describing different jars.

RSocket is rsocket-java 1.1.5, and spring-boot-starter-rsocket brings jackson-dataformat-cbor on purpose: CBOR, not JSON, is Spring's default RSocket data mime type.


Next: 2. How they measure