Quarkus vs Spring Boot: What You Need to Know

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!

spring boot vs quarkus

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).

quarkus vs spring boot

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)

spring boot vs quarkus

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:

compare spring boot quarkus

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:

quarkus vs spring boot

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.

spring boot vs quarkus

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