Introduction
This article tests three distinct Spring Boot application architectures:
- Classic Servlet App (Servlet)
- Reactive
- RSocket
Motivation
HTTP is a widely used transport for APIs, and it has pros and cons. It’s old and has big performance issues, but it is still widely used because of its ease of use.
Assumptions
- Basic applications without best practices implementation
- Tests may not be identical in every aspect
- JVM parameters remain unoptimized for each application type
Requirements
- Accept messages, receive lists/streams, save to database with empty response
- List messages by author with return of all messages
- Identical data feeds across all tests
- Measure requests per second (RPS)
Message Format:
{
"id": "UUID",
"text": "String",
"author": "String",
"createdAt": "ZonedDateTime"
}
Test Scenario
Create Messages
- Iterate concurrent threads (4, 8, 16, 64)
- Test message sizes (1, 10, 100, 1000) in single request
- Run test, clean database, sleep, repeat
Get Messages
- Iterate concurrent threads (4, 8, 16, 64)
- Generate test messages
- Run test, clean data, sleep, repeat
Execution Environment
- Java Version: OpenJDK 21.0.5
- Processor: Intel Core i9-14900HX
- Memory: 32540280 kB
Performance Results
Send Messages (RPS)
| Threads | Servlet | Reactive | RSocket |
|---|---|---|---|
| 4 | 3691.5 | 18668.4 | 20862.3 |
| 8 | 5088.7 | 15821.7 | 20362.9 |
| 16 | 4497.7 | 13346.9 | 21043.3 |
| 64 | 4501 | 12443.3 | 20881.3 |
Get Messages (RPS)
| Threads | Servlet | Reactive | RSocket |
|---|---|---|---|
| 4 | 1786 | 7270 | 20862.3 |
| 8 | 3600 | 5367 | 20362.9 |
| 16 | 3502 | 2914 | 21043.3 |
| 64 | 3535 | 2064 | 20881.3 |
Analysis
Servlet
Servlet is a legacy interface that is not good for asynchronous calls. With the latest API it provided an opportunity for async dispatch, but even with it had bad performance.

Servlet workflow issues:
- Requires collecting messages on sender side
- JSON serialization of entire collection
- Server must accept complete HTTP request before processing
- Requires deserialization of entire payload
- Memory demands lead to GC pauses and potential OOM
Reactive
Spring Boot Reactive uses Netty instead of traditional servlet containers, enabling async operations.

Key advantage: application/x-ndjson allows us to send/receive messages in a more convenient way for faster processing without blocking and awaiting all HTTP request/response.
Payload comparison:
application/json:[{..}, {..}]application/x-ndjson:{}\n{}\n
TCP Dump - application/json:

TCP Dump - application/x-ndjson:

Reactive implementation example:
class Controller {
public Mono<Void> addMessages(@RequestBody Flux<Message> messages) {
return messages.flatMap(message -> r2dbc.insert(Message.class).using(message))
.then();
}
}
Benefits: Non-blocking HTTP processing, streaming support, incremental message transmission.
Limitations: Database becomes bottleneck; stream failures affect entire request; lacks full backpressure.
RSocket
RSocket is reactive binary protocol that builds over TCP.

Key advantages:
- Operates over TCP, not HTTP
- Implements backpressure mechanism
- Consumer could ask publisher to stop producing more messages if it overloaded or give more if it have enough capacity
- Provides superior linear scalability
RSocket demonstrates consistent high performance across all concurrency levels.
Summary
Servlet bottlenecks: Blocking operations, HTTP protocol limitations, memory accumulation.
Reactive improvements: Non-blocking APIs, streaming between client/server, efficient I/O handling.
RSocket advantages: Complete reactive paradigm implementation, backpressure support, binary protocol efficiency.
References
- Code: https://github.com/alimovalisher/reactive-tests
- Spring Boot Reactive: https://spring.io/reactive
- Flux/Project Reactor: https://projectreactor.io
- RSocket: https://rsocket.io