Quarkus Vs Spring Boot: A Real-world Performance Comparison
Introduction
After reading a lot about Quarkus and doing several promising benchmark tests (like this feature and load test comparison with Spring Boot), I decided to migrate my monitoring microservice web app from Spring Boot to Quarkus. The tech stack used initially by this web app is as follows:
- JDK 14
- Kotlin
- Spring Boot v2.3.0.RELEASE: @RestController, @Service, @ExceptionHandler, @Scheduled, @ConfigurationProperties
- Spring WebFlux (with Netty)
- Spring Reactive WebClient
- Project Reactor
- Jackson
- Logback
- Gradle (Groovy DSL)
After the migration, the app's stack looks like this:
- JDK 14
- Kotlin
- Quarkus v1.4.2.Final: JAX-RS, CDI, ExceptionMapper, Quarkus Scheduler, MicroProfile Config
- Eclipse Vert.x (with Netty)
- Vert.x Mutiny WebClient
- SmallRye Mutiny
- Jackson
- Logback
- Gradle (Kotlin DSL)
For those interested, I have shared many Quarkus migration tips in the following article: https://simply-how.com/migrate-spring-boot-app-to-quarkus
This article will compare several key JVM metrics collected during the first 24 hours of the deployment in production (with a 30 minute offset from startup): Heap Memory, Non-Heap memory, Garbage Collection count and time, loaded class count and thread count. Elasticsearch and Kibana were used to collect and display the graphics.
Memory usage
As demonstrated by the illustrations, the heap memory usage was reduced by about:
- 55 MB (Average)
- 70 MB (Max)
- 38 MB (Min)
The non-heap memory usage was reduced by about 12 MB.
The Spring Boot app's committed heap memory already reached the max (the max memory was originally tuned for it), while Quarkus freed up 111 MB to the operating system.
Overall, I was able to save about 24% of the original memory (Heap).
Other metrics
In a 24 hour time frame, Spring Boot had done 131 GCs that lasted a total of 2 seconds and 616 milliseconds, while Quarkus did only 39 GCs that lasted a total of 1 seconds and 299 milliseconds.
We can also see that Spring Boot does about 1 GC each 10 minute while Quarkus does 1 GC each 30 minutes.
Quarkus loaded 2119 fewer classes and had 6 less median thread count.
The average CPU usage is similar, with a slightly higher peak for Spring Boot.
Benchmarks
Time to first request
You can find the script used to collect the time to first request in the following repository.
Quarkus is by far the winner in this category with more than 6 seconds of difference.
Request per second
As suggested by this official Quarkus blog post, the hello endpoint used for the benchmarking was implemented using reactive routes in order to run purely on the IO thread.
Loadtest
Quarkus had a higher request per second and lower latencies using the loadtest tool.
Wrk
Quarkus is still the clear winner with twice as much rps as Spring Boot when using the wrk tool.
Conclusion
Overall, Quarkus had better memory usage, garbage collection & JVM metrics, time to first request and simple request throughput.
Quarkus is the clear winner for this use case.
I have open sourced a big part of the web app presented in this tutorial, more specifically the analytics microservice:
- The Sprint Boot version's source code can be found in this repository
- The Quarkus version's source code can be found in this repository
For even more performance gains, the next step would be to migrate Quarkus from JDK to GraalVM native image. I have shared in this article some tips to help you fix some issues that may block you when building using GraalVM Native.
Soufiane Sakhi is an AWS Certified Solutions Architect – Associate and a professional full stack developer.