1
0
Files
spring-messaging-demo/protocol-comparison

protocol-comparison — RSocket vs gRPC vs WebSocket, same use case, measured

Companion project for RSocket vs gRPC vs WebSocket on Spring Boot 4.1: When Each One Wins on ankurm.com.

One application serves the same two operations over all three protocols. Same JVM, same heap, same data generator, same five fields — so a difference in the numbers is a difference in the transports rather than in three separately-written demos. ./scripts/run-all.sh re-measures everything under docs/output/.

Versions

Version Notes
JDK 25 (Temurin 25.0.4.1+1) current LTS
Spring Boot 4.1.1 latest GA of the 4.1 line
Spring Framework 7.0.9 Boot-managed
grpc-java 1.83.1 Boot-managed. Boot 4.1.0 pinned 1.80.0 — it moved
protobuf-java 4.35.1 Boot-managed. Boot 4.1.0 pinned 4.34.2
rsocket-java 1.1.5 Boot-managed
Spring gRPC 1.1.1 the programming model; separate version from Boot's
Jackson 3.1.5 (tools.jackson) plus jackson-dataformat-cbor, RSocket's default

Read from repo1.maven.org/.../maven-metadata.xml and Boot's own spring-boot-dependencies POM.

Quickstart

./scripts/run-all.sh     # six tests, then re-measure docs/output/
mvn test                 # the same without the capture
mvn spring-boot:run      # gRPC 9090, RSocket 7000, WebSocket ws://localhost:8080/quotes

Nothing to install: protoc and the gRPC codegen plugin resolve as Maven artifacts for your OS and architecture, and all three servers start inside the test JVM.

The operations

gRPC RSocket route WebSocket command
one quote QuoteService/GetQuote quote QUOTE <symbol>
N quotes QuoteService/StreamQuotes quotes STREAM <symbol> <n> <delayMs>
unbounded QuoteService/StreamUnbounded quotes.unbounded UNBOUNDED <symbol>
same, isReady()-gated QuoteService/StreamUnboundedReady
server-side produced count QuoteService/Produced produced PRODUCED

Documentation

  1. The same use case, three ways
  2. How they measure — and why a loopback benchmark flatters the wrong one
  3. Payload: the one comparison that needs no benchmark
  4. Back-pressure: the measurement that decides it
  5. Choosing

Captured output

File What it shows
payload-sizes.txt one message as protobuf, JSON and CBOR, in hex
request-response.txt 5 000 sequential calls, p50/p99/mean per protocol
stream-throughput.txt 50 000 messages on one connection
backpressure.txt how many the server produced for a client that wanted 100
grpc-isready.txt the documented gRPC fix, tested — and it did not work here
rsocket-codecs.txt what Boot configures for RSocket, and why CBOR wins
tests.txt 6 tests

Read the benchmark honestly

Client and server are the same process on one loopback interface, two cores. There is no network. That deletes protobuf's size advantage, and it flatters whichever protocol does the least work per message — which is why raw WebSocket wins both speed benchmarks and why that is not the recommendation. Treat the ratios as indicative and the absolutes as meaningless off this box.

The one result that does transfer is the back-pressure table, because it is a property of the protocols rather than of the link.

Four things this module exists to prove

  1. RSocket's request(n) is real and the others have no equivalent. For a client that took 100 messages then went quiet for two seconds, the servers produced 100, 1 346 233 and 311 617 respectively.
  2. ServerCallStreamObserver.isReady() did not bound a gRPC producer here. The documented fix was tested; the transcript is committed as measured, including its instability.
  3. Raw WebSocket is the fastest and has the worst tail. p50 of 124 µs against a p99 of 1 448 µs — a 12x spread, where gRPC's is 3.5x.
  4. CBOR keeps the field names, and it wins by list order. It is binary JSON, not a schema format: 67 bytes against protobuf's 35 and JSON's 83. Boot puts JacksonCborEncoder ahead of JacksonJsonEncoder, and a bare RSocketStrategies.create() has neither.