In this two-part article series, we will be comparing Quarkus vs Spring Boot taking into account aspects related to the performance and runtime statistics of a simple example application. In the next part, we will cover other key aspects such as cloud-native readiness. Get ready: this is Quarkus vs Spring Boot!
First off, in terms of product, Quarkus is an OpenSource product developed by Red Hat engineers. The enterprise version of it is Red Hat Build of Quarkus.
Spring Boot is as well an Open Source project developed by the Pivotal team which provides as well Enterprise Support for Spring Boot. Spring Boot is supported as well by Red Hat as part of Red Hat Runtimes portfolio (https://access.redhat.com/products/red-hat-runtimes)
Quarkus vs Spring Boot: project bootstrap
Where does our journey begins? from application startup obviously ! You can bootstrap your Quarkus and Spring Boot applications with a variety of tools. Let’s check the available options:
To get started with Quarkus you have several options:
- Using the Maven and Gradle plugins which create for you a project with the default structure
- Online (https://code.quarkus.io/) directly from the Cloud.
- Using the Quarkus Command Line tool, since Quarkus 2.x
- Installing the plugin for your favourite IDE (including IntelliJ, JBoss Tools for Eclipse, Visual Studio, Eclipse Che)
To get started with Spring Boot you also have a variety of choices:
- Using the Online Initializer (https://start.spring.io/).
- Through the powerful Spring Command Line Interface (https://repo.spring.io/release/org/springframework/boot/spring-boot-cli/).
- Using the plugin for your favourite IDE including IntelliJ, Visual Studio, or the most complete choice which is Spring Tools (https://spring.io/tools)
Comparing the runnable JARs
As both Spring Boot and Quarkus target a serverless, micro service environment, you can produce runnable Jar files out of your applications. We will be comparing a simple application using a REST Controller backed by a minimal Persistence layer and see what we get from each framework. The example applications I will use for this purpose are:
- Quarkus Hibernate Quickstart ()
- Spring Boot CRUD application with the same features ().
Upon building both applications as pure Java applications, here is the resulting application:
Quarkus Application:
11046 Dec 20 13:14 hibernate-orm-quickstart-1.0.0-SNAPSHOT.jar
The runner jar of Quarkus is a tiny one, however, is not an Uber file. The external libraries are in the target/quarkus-app/lib folder.
$ du -sh ./target/quarkus-app/lib/ 31M ./target/quarkus-app/lib/
Spring Boot application consists in a single Uber Jar file:
36214416 May 1 09:56 cruddemo-0.0.1-SNAPSHOT.jar
So on a balance Spring Boot produced an all-in-one Uber jar (about 36 MB), while Quarkus packages a wrapper Runnable Jar with a set of libraries (about 31 MB in total).
Comparing start-up time
Aside from the resulting artifacts, it is way more interesting to check the start time of applications, which is a key factor for microservices applications. Let’s start comparing the boot time for both applications, packaged as Java regular applications:
The Quarkus application, launched as Java application, took about 1.7 seconds to start:
$ java -jar ./target/quarkus-app/quarkus-run.jar 2021-12-20 13:29:27,179 INFO [io.quarkus] (main) hibernate-orm-quickstart 1.0.0-SNAPSHOT on JVM (powered by Quarkus 2.5.3.Final) started in 1.764s. Listening on: http://0.0.0.0:8080 2021-12-20 13:29:27,182 INFO [io.quarkus] (main) Profile prod activated. 2021-12-20 13:29:27,183 INFO [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-postgresql, narayana-jta, resteasy, resteasy-jackson, smallrye-context-propagation, vertx]
The Spring Boot application, on the other hand, required about 4.6 seconds to start:
2020-05-01 10:08:34.739 INFO 13417 --- [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page template: index 2020-05-01 10:08:34.900 INFO 13417 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2020-05-01 10:08:34.902 INFO 13417 --- [ main] com.example.cruddemo.DemoApplication : Started DemoApplication in 4.604 seconds (JVM running for 5.07)
Let’s check the memory usage of both applications.
Comparing Memory Usage
In terms of memory usage,let’s launch VisualVM to monitor the amount of Memory used by both applications.
$ java -jar ./target/quarkus-app/quarkus-run.jar
Here is the Heap size of the Quarkus application. As you can see, it is currently using about 61 MB:
Let’s now check the Spring Boot application:
$ java -jar target/cruddemo-0.0.1-SNAPSHOT.jar
The Spring Application is currently using about 172 MB, so almost 300% more than the Quarkus application:
Native executable with Quarkus and Spring Boot
One of the biggest highlights of Quarkus is its ability to create native cloud-ready applications, once you have installed GRAALVM and configured the native-image tooling. This brings amazing improvements in terms of memory usage and especially application start-up. In Quarkus you can generate a native executable from your application by using its tooling and the “native” profile:
$ mvn verify -Pnative
Once completed the build, we will launch the application:
$ target/hibernate-orm-quickstart-1.0-SNAPSHOT-runner --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2020-05-03 09:44:05,171 INFO [io.quarkus] (main) hibernate-orm-quickstart 1.0-SNAPSHOT (powered by Quarkus 1.3.2.Final) started in 0.077s. Listening on: http://0.0.0.0:8080 2020-05-03 09:44:05,171 INFO [io.quarkus] (main) Profile prod activated. 2020-05-03 09:44:05,171 INFO [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-postgresql, narayana-jta, resteasy, resteasy-jsonb]
As you can see, it took just 0.077s to start the application. On the other hand,in terms of memory usage the Quarkus native application uses just a fraction of its Java counterpart: barely 44 MB:
ps -eo pid,vsz,rss,comm | grep hibernate 16376 1648624 44296 hibernate-orm-q
On the Spring Boot side, you can use Spring Native to support native compilation of Spring Boot applications using the GraalVM native-image compiler.
The core feature is the spring-graal-native-feature which is a Spring GraalVM feature for the native-image compilation process.
After adding Spring Native support to our Spring REST application, we have run it:
$ target/crud-demo 2021-12-20 18:41:11.216 DEBUG 8345 --- [ main] o.s.d.r.w.BasePathAwareHandlerMapping : 5 mappings in org.springframework.data.rest.webmvc.BasePathAwareHandlerMapping 2021-12-20 18:41:11.223 INFO 8345 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2021-12-20 18:41:11.224 INFO 8345 --- [ main] com.example.data.rest.Application : Started Application in 0.27 seconds (JVM running for 0.271)
As you can see, the application started in 0.27 seconds.
In terms of memory usage, here is the report:
ps -eo pid,vsz,rss,comm | grep crud-demo 8345 1189628 239060 crud-demo
Overall, the Spring Boot native image has a Process Resident Size of about 239 MB.
Cloud readiness: Quarkus vs Spring Boot
Both Spring Boot and Quarkus are cloud-ready solutions, meaning that they can be deployed in a platform capable of managing containerized workloads such as Kubernates or OpenShift. Let’s check how we can build a container image of our application in both cases.
When you build your Quarkus application with any available starter, it will create for you a distinct folder named “docker” which already includes the following Dockerfiles:
src ├── main │ ├── docker │ │ ├── Dockerfile.jvm │ │ ├── Dockerfile.legacy-jar │ │ ├── Dockerfile.native │ │ └── Dockerfile.native-distroless
Therefore, once you have packaged your application using the option quarkus.native.container-build=true:
$ mvn package -Pnative -Dquarkus.native.container-build=true
Then it’s just a matter of building and running the Docker file you want to use:
$ docker build -f src/main/docker/Dockerfile.native -t quarkus/hibernate-orm . $ docker run -i --rm -p 8080:8080 quarkus/hibernate-orm
Let’s check some stats about the Quarkus application when run in a Docker container using ‘docker stats’:
CONTAINER CPU % MEM USAGE / LIMIT hibernate-orm 0.09% 293.7 MiB / 30.8 GiB
Let’s see how to build a Container image of a Spring Boot application. There are several approaches to it. As Spring Boot delivers an Uber Jar file, most developers create a basic Docker file which starts from the openjdk:8-jdk-alpine (or newer) and add the Spring Application on the top of it:
FROM openjdk:8-jdk-alpine VOLUME /tmp EXPOSE 8080 ARG JAR_FILE=target/cruddemo-0.0.1-SNAPSHOT.jar ADD ${JAR_FILE} cruddemo-0.0.1-SNAPSHOT.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/cruddemo-0.0.1-SNAPSHOT.jar"]
When building and running the Spring Boot CRUD Docker image, we will have the following statistics from the Docker image:
CONTAINER CPU % MEM USAGE / LIMIT cruddemo 0.09% 464.1 MiB / 30.8 GiB
The overall memory usage of the Quarkus and Spring Boot images are greatly influenced by the distros on which the application layer runs, however the Quarkus one shows smaller memory usage and can be kick-started for Docker without any additional coding. It is worth mentioning, however, that since Spring Boot 2.3.0.M1, it will be possible to have in your Spring Boot projects support for building Container images just by running:
mvn spring-boot:build-image
Applications Live reload
Live reload is an essential feature for Java developers, so that they can update their application code and see the result without restarting the Java process. Both Quarkus and Spring Boot feature a live reload of your applications. Let’s compare their approach.
On the Quarkus side, the live reload feature is totally transparent when running in development mode. Every change to your code or configuration files results in a transparent live reload of your application:
2020-05-03 11:01:38,201 INFO [io.qua.dev] (vert.x-worker-thread-0) Hot replace total time: 0.942s
It is worth noticing that Live reload can be triggered also on a remote environment, so for example on a Kubernates/OpenShift cluster, by passing the live reload URL of the remote host at start:
$ mvn quarkus:remote-dev -Dquarkus.live-reload.url=http://my-remote-host:8080
On the Spring Boot side, live reload can be instrumented in your applications by including the spring-boot-devtools in your configuration:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency>
Then, provided that you have recompiled the application code, you will be able to see changes live without restarting the Spring Boot process.
Summary
At the end of this first round, we have checked how a basic CRUD application performs when using Quarkus and Spring Boot. For the time being, Quarkus has the evident advantage of being designed from the grounds up to be cloud-native ready, using a minimal memory footprint and development joy, thanks to its built-in live reload feature. On the other side, Spring Boot applications, which produce an all-in-one Uber Jar, can also easily plugged into any JDK based distribution to produce cloud-ready applications. In the next article, we will move our comparison on another field, the core libraries/extensions which make the ecosystem of both frameworks. Continue reading the part 2: Quarkus vs Spring Boot – Part 2