Despite being a fan of Spring Framework, every now and then I experience a feeling of “stomach heaviness” employing it in the micro-service architecture. Fast to create, a tiny SpringBoot REST API with a mundane business logic and RDBMS connectivity usually encounters up to tens of thousands of classes and consumes 300–500 MB of RAM at runtime. One can argue that memory and computational resources are commodities nowadays and the flip side of the Spring’s cumbersomeness are resilience and stability. While it’s definitely true to some extent, I refuse to believe that stable application can’t be lean and efficient. And that’s where relatively new technologies like Quarkus, Micronaut or Microprofile can be handy to prove.
In Yilu we are developing a complex and modular end-to-end platform for trips that supports travellers by supplying them with the most relevant information about their destinations and bookings, as well as promotes tailored services and attractions that help to make the journey really easy and fun. Our platform is built of micro-services running in the Kubernetes and any of the new lean frameworks can be a good fit for it. Thus planning of next part of the platform spawns the same vicious cycle of doubts about software efficiency vs. introduction of a new technology into the existing tech stack.
I’ve been watching the development of Quarkus and Micronaut for a couple of years already and even applied them in projects. Despite the huge progress and my (mostly intuitive) appreciation of those frameworks, I’m very hesitant to recommend any because of a lack of credibility and acceptance as well as very little practical evidence supporting their benefits. Despite bright and promising assertions of superiority of new frameworks over SpringBoot and each other, existing reviews, guides, and comparisons are mostly based on “Hello world” examples that can hardly benchmark any real life performance and coding experience. Thus I didn’t come up with anything better than to make my own experiment and to find out whether SpringBoot can be easily beaten by one of those frugal frameworks.
This article is targeted towards developers, architects and managers that look for additional information on migration/extension of existing SpringBoot tech stack with lean Java frameworks. This is not a step-by-step guide, but rather a short overview of the author’s experience in the parallel development of two versions of the same micro-service: a SpringBoot application and a Quarkus app. It also discusses some runtime metrics of those applications. The service in question is a real part of a complex platform, that works with real-life data against production APIs and databases.
For those who are seeking for a fast advice and not interested in reading details, here is a short overview (see more details in conclusion):
- the overall development experience is comparable. The author had a significant exposure to Spring and almost negligible experience with Quarkus, thus the start of the development indeed required some learning of Quarkus basics, including Mutiny — a reactive programming library that replaced Project Reactor in Quarkus and Vert.x. Nevertheless estimated development time of both versions of the app was pretty equal.
- Quarkus integrates numerous seasoned technologies (many of them are also employed in Vert.x) so it’s not something entirely new, however, in contrast to the Spring’s projects, these technologies have yet to be aligned to each other. This fact can cause some hiccups during development and, again, requires some learnings and workarounds.
- App scaffolding for both frameworks is straightforward. Both have web pages where one can configure and scaffold the future app as well as IDE plugins with the similar features.
- Unfortunately a SpringBoot code can’t be directly ported to a Quarkus app, mainly due to the different technologies that are used to perform similar functions. Nevertheless Quarkus declares integration with SpringBoot projects as well as ability to use the SpringBoot’s CDI mechanism. I’m not sure what consequences this mixture will have for the app efficiency and size, but probably it will result in some compromises.
- in comparison to Quarkus, SpringBoot has a way better documentation that is pretty handy in development. Most of the problems one can encounter are widely discussed at StackOverflow and GitHub. Quarkus’ documentation in many cases is scarce. Some additional information can be found on websites of corresponding technologies or Vert.x.
- The technologies employed by Quarkus feel to be “lighter” and more straightforward in use, that compensated a bit the scarcity of documentation.
- Metrics-wise Quarkus showed a significant superiority for some of the measured indicators:
Basically both apps show a comparable productivity, but the Quarkus’ memory footprint was five times lower than the SB’s one (under a moderate load).
Building the app
The application in question is a trivial data transformation service that fetches information from an external REST API, transforms it, saves in RDBMS (Postgres) and exposes it to consumers via GraphQL API. An SMTP listener is used to trigger updates of the persisted data when external service is updated (an email notification is sent by the external API on every change). Component diagram of the service is like this:
Reactive programming library
Recently Quarkus, following Vert.x and Microprofile, adopted a new reactive programming framework Mutiny as the primary implementation of Java’s Reactive Streams. Despite the new library has a lot in common with Project Reactor and RxJava, it took some time and learning to get used to its overly verbose (in comparison to Project Reactor) syntax and the overall paradigm. Worth mentioning though, that some of Mutiny’s operators felt to be more straightforward compared to their analogs in Reactor.
The SpringBoot app uses the seasoned Reactor’s Mono and Flux. Polished documentation and widespread usage often make it to be a choice No1 for development of reactive applications.
App config and CDI
Quarkus employs an extremely simple CDI approach described at one of its tutorials. Needles to say that this simplified the app configuration and amount of boilerplate code comparing with SB, but the overall experience is very similar for both frameworks.
Quarkus documentation describes integration of the SmallRye GraphQL server into the Quarkus app. This library is also used in MicroProfile and designed with code-first in mind. This approach and a pretty detailed tutorial ensured a very smooth coding experience.
SpringBoot doesn’t provide any reference to officially supported GraphQL implementation, thus, after short research, the GraphQL Java Kickstart was picked for the experiment. This library requires an upfront schema definition and some boilerplate code for its implementation.
Quarkus has the Vert.x core under the hood, and thus inherits its web stack. Experience with the Vert.x WebClient doesn’t really differ from the WebFlux implementation. What felt missing is Spring Security. OAuth authentication mechanism had to be written from scratch.
Another part which is readily available in SpringBoot (through Spring Integration) is Email clients. Quarkus proposes to use Apache Camel to integrate emails, however the author felt it to be an overkill in this particular case, since the mailing module would also require the Camel’s core and several other elements. So the IMAP client was created using the low level javax.mail library.
Both variants use reactive Postgres clients to access RDBMS. Quarkus makes use of the one from Vert.x. For the SpringBoot app the R2DBC implementation is proposed in the official docs. Both implementations are pretty similar. The R2DBC version proposes also JPA-like repositories, but for more or less complex queries the fallback to the bare sql client was required.
Fat Jar build time of the Quarkus app was expectedly twice longer (5 s) then for the SpringBoot counterpart (3 s). Quarkus was faster to start: around 7s vs 9 s for SB. Further testing consisted of 3 stages:
- approx. 2 minutes of retrieving and processing data from an external API (1 HTTP call every 10 seconds);
- the 2 minutes long load test with progressive scaling from 0 to 100 simultaneous requests to GraphQL API (alongside with continuing data processing of the first stage);
- the 2 minutes long load test with progressive scaling from 0 to 1000 simultaneous requests to GraphQL API (alongside with continuing data processing of the first stage).
As graphs show, Quarkus consumes less CPU power during the 1st phase, but causes a significant load responding API calls.
Quarkus App’s Heap:
SpringBoot App’s Heap:
Quarkus App’s Metaspace:
Quarkus App’s Metaspace:
The difference in memory consumption between two frameworks is drastic. Quarkus consumed five times less memory under the moderate load. The memory consumption started to grow only in response to more than 200 concurrent requests. SpringBoot in its turn, outperformed Quarkus when the load reached approximately 700 simultaneous calls.
Quarkus (0–100 concurrent calls):
SpringBoot (0–100 concurrent calls):
Quarkus (0–1000 concurrent calls):
SpringBoot (0–1000 concurrent calls):
(0–1000 concurrent calls)
The average latency of the SB API is 4 times lower than the one of Quarkus. The API scalability was also way better at the SB app.
The overall impression I got after having conducted this experiment is “it depends”. Being more familiar with Spring and having in mind its elaborated documentation I would argue that the development of SpringBoot app was relatively smoother. Not critical, though. The Quarkus team is doing a really good job in updating and extending documentation. Most of the deficiencies can be explained by the novelty of the framework itself and integrated technologies in particular, and thus should be eliminated over the time.
Memory efficiency of Quarkus is really impressive, it also outperformed SpringBoot in CPU load under a moderate pressure. This article doesn’t cover Graal-VM — a tandem partner, that helps Quarkus to fully shine in its glory. According to some benchmarks usage of Graal-VM helps to improve Quarkus’ metrics and resource consumption even more. This, along with compilation to native app, can be a good ground for another paper.
Pretty weak HTTP metrics of Quarkus most probably were caused by deficiencies of the SmallRye GraphQL library. I conducted a couple of other load tests and intuitive benchmarks (not covered here), that confirmed this guess. But this also proves the fact that some Quarkus technologies are still slack-baked and need to be considered carefully.
As a general conclusion, I would rather abstain from using Quarkus for critical and heavy-loaded applications yet, but definitely give it a try for supportive services.