# 1. The same use case, three ways Next: [2. How they measure](02-benchmarks.md) --- 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`](../src/main/java/com/ankurm/protocols/QuoteSource.java), 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`](../src/main/java/com/ankurm/protocols/GrpcQuoteService.java)) is generated-class inheritance. There is no registration code: the generated `ImplBase` is a `BindableService` and Boot registers every such bean. ```java @Override public void getQuote(QuoteRequest request, StreamObserver observer) { observer.onNext(toProto(source.at(request.getSymbol(), 0))); observer.onCompleted(); } ``` **RSocket** ([`RSocketQuoteController`](../src/main/java/com/ankurm/protocols/RSocketQuoteController.java)) is annotation routing, and the interaction model *is the return type*: ```java @MessageMapping("quote") public Mono quote(String symbol) { ... } // request/response @MessageMapping("quotes") public Flux quotes(StreamSpec spec) { ... } // request/stream ``` **Raw WebSocket** ([`WebSocketQuoteHandler`](../src/main/java/com/ankurm/protocols/WebSocketQuoteHandler.java)) is a `switch` on a string, because there is nothing else: ```java 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`](../../sse-websocket/README.md). ## 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](02-benchmarks.md)