In this two-part article series, we will be comparing Quarkus and Spring Boot taking into account aspects related to the runtime statistics of an application used by them, the core libraries/framework used along with 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 and also supported as a Product by Red Hat as a Product (https://access.redhat.com/products/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)

 

Project bootstrap

Where does our journey begin? from how to get started obviously! Both Quarkus and Spring Boot aren't middleware products, in the sense that you don't have to download any server/container to develop applications with them. You can bootstrap your applications by using the available tooling. Let's check the available options:

To get started with Quarkus you have several options:

  • The Maven and Gradle plugin which create for you a project with the default structure
  • The online starter tool (https://code.quarkus.io/) which can be used to bootstrap your project from the Cloud.
  • Using 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/).
  • Using 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)

The artifacts produced by Quarkus and Spring Boot

As both Spring Boot and Quarkus target a serverless, microservice 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 (https://bit.ly/2VVtsLD) and a minimal Spring Boot CRUD application with the same features (https://bit.ly/2VX2vHv).

Upon building both applications as pure Java applications, here is what we have got:

Quarkus Application:

349645 May  1 19:48 hibernate-orm-quickstart-1.0-SNAPSHOT-runner.jar

The runner jar of Quarkus is a tiny one, however, is not an Uber file. The external libraries are included in the target/lib folder.

$ du -sh target/lib
  
30M	target/lib

Spring Boot application consists in an Uber Jar file with all libraries bundled:

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 backed by its libraries (about 30 MB in total).

Application 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.8 seconds to start:

2020-05-01 10:05:17,779 INFO  [io.quarkus] (main) hibernate-orm-quickstart 1.0-SNAPSHOT (powered by Quarkus 1.3.2.Final) started in 1.805s. Listening on: http://0.0.0.0:8080
2020-05-01 10:05:17,781 INFO  [io.quarkus] (main) Profile prod activated. 
2020-05-01 10:05:17,781 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-postgresql, narayana-jta, resteasy, resteasy-jsonb]

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.

Memory Usage

In terms of memory usage, the most interesting indicators are the Resident Set Size (RSS) and Virtual memory Size. Just as a recap:

  • RSS is how much memory this process currently has in the main memory (RAM).
  • VSZ is how much virtual memory the process has in total. This includes all types of memory, both in RAM and swapped out.
$ java -jar target/hibernate-orm-quickstart-1.0-SNAPSHOT-runner.jar

$ ps -eo pid,vsz,rss,comm | grep java
14949 12568124 291476 java

As the output of RSS column is by default expressed in KB, the Quarkus application is currently running usinh about 291 MB. Let's now check the Spring Boot application:

$ java -jar target/cruddemo-0.0.1-SNAPSHOT.jar

$ ps -eo pid,vsz,rss,comm | grep java
21164 12454952 678856 java

The Spring Application is consuming about 678 MB, which is more than double of the Quarkus application.

Native executable applications

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, at the moment, there is experimental support for building Spring Boot applications as GraalVM native images as annonced in a recent Devoxx talk. The project is hosted on github at: https://github.com/spring-projects-experimental/spring-graal-native.

The core feature is the spring-graal-native-feature which is a Spring GraalVM feature for the native-image compilation process.

Having configured GRAALVM and the native image plugin, I've attempted to build a native image out of my Spring Boot CRUD application, including the native-image-maven-plugin in the project:

 <profile>
            <id>graal</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.graalvm.nativeimage</groupId>
                        <artifactId>native-image-maven-plugin</artifactId>
                        <version>20.0.0</version>
                        <configuration>
                            <buildArgs>
-Dspring.graal.mode=initialization-only -Dspring.graal.dump-config=/tmp/computed-reflect-config.json -Dspring.graal.verbose=true -Dspring.graal.skip-logback=true --initialize-at-run-time=org.springframework.data.r2dbc.connectionfactory.ConnectionFactoryUtils --initialize-at-build-time=io.r2dbc.spi.IsolationLevel,io.r2dbc.spi --initialize-at-build-time=io.r2dbc.spi.ConstantPool,io.r2dbc.spi.Assert,io.r2dbc.spi.ValidationDepth --initialize-at-build-time=org.springframework.data.r2dbc.connectionfactory -H:+TraceClassInitialization --no-fallback --allow-incomplete-classpath --report-unsupported-elements-at-runtime -H:+ReportExceptionStackTraces --no-server --initialize-at-build-time=org.reactivestreams.Publisher --initialize-at-build-time=com.example.reactive.ReservationRepository --initialize-at-run-time=io.netty.channel.unix.Socket --initialize-at-run-time=io.netty.channel.unix.IovArray --initialize-at-run-time=io.netty.channel.epoll.EpollEventLoop --initialize-at-run-time=io.netty.channel.unix.Errors
                            </buildArgs>
                        </configuration>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>native-image</goal>
                                </goals>
                                <phase>package</phase>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                    </plugin>
                </plugins>
            </build>
</profile>

 The full process is documented in Spring Boot blog page: https://spring.io/blog/2020/04/16/spring-tips-the-graalvm-native-image-builder-feature
At the end of the day, however I've hit some issues in relation to its dependencies:

Caused by: java.lang.RuntimeException: Problem during scan of ~/git/spring-graal-native/cruddemo/target/native-image/BOOT-INF/lib/jakarta.xml.bind-api-2.3.2.jar
	at org.springframework.graal.type.TypeSystem.indexJar(TypeSystem.java:328)
	at org.springframework.graal.type.TypeSystem.index(TypeSystem.java:279)
	at org.springframework.graal.type.TypeSystem.<init>(TypeSystem.java:81)
	at org.springframework.graal.type.TypeSystem.get(TypeSystem.java:76)
	at org.springframework.graal.support.ResourcesHandler.register(ResourcesHandler.java:94)
	at org.springframework.graal.support.SpringFeature.beforeAnalysis(SpringFeature.java:79)
	at com.oracle.svm.hosted.NativeImageGenerator.lambda$runPointsToAnalysis$7(NativeImageGenerator.java:674)
	at com.oracle.svm.hosted.FeatureHandler.forEachFeature(FeatureHandler.java:63)
	at com.oracle.svm.hosted.NativeImageGenerator.runPointsToAnalysis(NativeImageGenerator.java:674)
	at com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:530)
	at com.oracle.svm.hosted.NativeImageGenerator.lambda$run$0(NativeImageGenerator.java:445)
	at java.base/java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1407)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Caused by: java.nio.file.NoSuchFileException: ~/git/spring-graal-native/cruddemo/target/native-image/BOOT-INF/lib/jakarta.xml.bind-api-2.3.2.jar

To be honest I didn't manage to go much in detail on how to fix this issue but I'm confident the whole process will become more linear in the next releases. That beind said, however for the time being, Spring Boot support for native application is still in an experimental phase.

Cloud readiness

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 two Dockerfile: one for building JVM images and the other for building native container images:

src
├── main
│   ├── docker
│   │   ├── Dockerfile.jvm
│   │   └── Dockerfile.native

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 from the Spring Boot 2.3.0.M1 release on, it will be possible to have in your Spring Boot projects support for building Container images just by running:

mvn spring-boot:build-image

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 months to come, some features, such as image integration and spring-graal-native-feature will also be available out of the box in Spring Boot, therefore the comparison between Quarkus and Spring Boot will be fairer and thus even more interesting. In the next article, we will move our comparison on another field, the core libraries/extensions which make the ecosystem of both frameworks. Stay tuned!

0
0
0
s2sdefault