In the container era (the “Docker era”) Java still leads the pack, but which is better? Spring Boot or Quarkus?

Who will be the most advanced? Spring Boot or Quarkus.

In the container age (the “Docker age”), there’s no denying the vitality of Java, whether you’re using it or not. Java has always had an advantage in terms of performance, mainly because of the layer of abstraction between code and the real machine, the cost of multiple platforms (write once, run anywhere – remember?). , which includes jVM-between (JVM: software machine that simulates what real machines do).

Today, there is no advantage in using a microservices architecture to build multi-platform (explain) things for something that always runs in the same place and on the same platform (Docker container-Linux) environment. Portability is less important now (perhaps more important than ever), and those additional levels of abstraction are not important.

That being said, let’s do a simple, raw comparison of two alternatives for generating microservices in Java: the very well-known Spring Boot and the lesser-known (yet to be) Quarkus.

opponents

What is Quarkus?

A set of open source technologies for GraalVM and HotSpot for writing Java applications. It offers (officially) super fast startup times and a lower memory footprint. This makes it an ideal choice for container and serverless workloads. It uses Eclipse microconfiguration files (JAX-RS, CDI, JSON-P), a subset of Java EE, to build microservices.

GraalVM is a general-purpose multilingual virtual machine (JavaScript, Python, Ruby, R, Java, Scala, Kotlin). GraalVM (especially Substrate VM) makes AOT compilation possible, converting bytecode to native machine code, thereby generating binaries that can be executed locally.

Keep in mind that not all features can be used in native execution, and AOT compilation has its limitations. Note this quote (quote from GraalVM team) :

We run a radical static analysis that requires a closed-world assumption, which means that all classes and all bytecodes accessible at run time must be known at build time.

So, for example, reflection and the Java Native Interface (JNI) will not work, at least out of the box (with some extra work). You can find the list of restrictions in the native Java restrictions document.

What is Spring Boot?

Is it true? Well, I just want to say one sentence (please skip), one sentence: Spring Boot builds on the Spring Framework and is, in fact, an open source Framework that provides an easier way to build, configure, and run Web-based Java applications. Make it a good candidate for microservices.

Ready for battle – Create Docker image

Quarkus mirror

Let’s create the Quarkus application so that we can wrap it in the Docker image later. Basically, we’ll do the same thing as the Quarkus introductory tutorial.

Create a project using Quarkus Maven prototype:

MVN IO. Quarkus: quarkus maven - plugin: 1.0.0. CR2: create - DprojectGroupId=ujr.com bat. Quarkus -DprojectArtifactId=quarkus-echo -DclassName="ujr.combat.quarkus.EchoResource" -Dpath="/echo"Copy the code

This leads to the structure of our project, as follows:

Note that two sample Dockerfiles (SRC /main/docker) are also created: one for normal JVM App images and one for Native App images.

In the generated code, we only need to change one thing, adding the following dependency, because we are generating JSON content.

<dependency> 
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-resteasy-jsonb</artifactId> 
</dependency>
Copy the code

Quarkus uses the JAX-RS specification throughout the RESTEasy project implementation.

Here’s our “whole” app:

That’s all, with the next command we can see that the application is running:

mvn clean compile quarkus:dev
Copy the code

In this mode, we also turned on hot deployment and background compilation. Let’s take a simple test to find out:

curl -sw "\n\n" http://localhost:8080/echo/ualter | jq .
Copy the code

Now that we see it working, let’s create the Docker image. From here to download GraalVM: HTTPS: / / github.com/graalvm/graalvm-ce-builds/releases.

Important! Do not download the latest version 19.3.0, it is not compatible with Quarkus 1.0, perhaps Quarkus 1.1 will. The version that should work now is GraalVM 19.2.1, get this.

Configure the main path of its environment variables:

# # the At macOS will be: export GRAALVM_HOME = / Users/ualter/Developer/quarkus/graalvm - ce - java8-19.2.1 / Contents/Home /Copy the code

Then install GraalVM’s Native Image in your environment:

$GRAALVM_HOME/bin/gu install native-image
Copy the code

Let’s build a native version for the current platform (in this case, a native executable for macOS).

mvn package -Pnative
Copy the code

** Quarkus-echo-1.0-snapshot-runner * If all is well, we can find a file named./target. /target/ quarkus-echo-1.0-snapshot-runner: No need for JVM (common: Java -cp app:lib/ :etc app.jar), which is a native executable binary file.

Let’s generate a Native Docker Image for our application. This command creates a Native image, which is a Docker image with a Linux Native executable application. By default, native executable files are created based on the current platform (macOS). Since we know that the generated executable is different from the platform of the container (Linux), we will instruct Maven to build from within the container, generating the native Docker image:

mvn package -Pnative -Dquarkus.native.container-build=true
Copy the code

At this point, you must have a Docker container running time, a working environment.

This file will be a 64-bit Linux executable, so naturally this binary won’t run on our macOS, it was built for our Docker container image. So, keep going…… Let’s go generate the Docker image……

docker build -t ujr/quarkus-echo -f src/main/docker/Dockerfile.native . 
  ## Testing it... 
docker run -i --name quarkus-echo --rm -p 8081:8081 ujr/quarkus-echo
Copy the code

With caveats about Docker image size:

The final Docker image is 115MB, but you can use the distroless version to make a very small Docker image. The Distroless image contains only your application and its runtime dependencies, and everything else (package managers, shells, or common programs found in standard Linux distributions) is removed. The Distroless image for our application is 42.3MB in size. The file. / SRC/main/docker/Dockerfile native – distroless has generated receipt it.

On Distroless Images: “Strictly limiting the content in a runtime container to what your application needs is a best practice adopted by Google and other tech giants that have used containers in production environments for years.”

Spring the boot image

At this point, presumably everyone knows how to make a normal Spring Boot Docker image, let’s skip the details? Just an important observation, the code is exactly the same. Or better yet, almost the same, since we’re using Spring Framework annotations, of course. That’s the only difference. You can examine every detail in the supplied source code (link below).

mvn install dockerfile:build 
## Testing it...
   docker run --name springboot-echo --rm -p 8082:8082 ujr/springboot-echo
Copy the code

contrast

Let’s start two containers, get them up and running a few times, and then compare Startup Time with Memory Footprint.

During this process, each container is created and destroyed 10 times. Later, their startup time and memory footprint are analyzed. The figures shown below are based on the average results of all these tests.

The startup time

Obviously, this aspect can play an important role when it comes to scalability and serverless architectures.

With regard to the Serverless architecture, in this model, typically a temporary container will be triggered by events to perform tasks/functions. In a cloud environment, the price is usually based on the number of executions, rather than some previously purchased computing capacity. Thus, the cold start here may affect this type of solution, since the container is (typically) only active when it is performing its task.

In extensibility, it is clear that if you need to scale out suddenly, startup time will define how long it takes for the container to be fully ready (up and running) in response to the loading scenario being rendered.

How sudden (needed and fast) the scenario, long cold starts can be worse.

Let’s see how they perform in terms of startup time:

Well, you may have noticed that it is another test option inserted in the startup time diagram. In fact, it is identical to the Quarkus application, but generated using a JVM Docker image (using Dockerfile.jvm). As we have seen, even applications using Docker Image and JVM Quarkus applications have faster Boot times than Spring Boot.

Without a doubt, the Quarkus Native app is the clear winner, being the fastest app to launch so far.

Memory footprint

Now, let’s examine memory. Examine how much memory each container application consumes at startup to get itself up and running, ready to receive requests.

conclusion

Anyway, this is what we see in Linux Ubuntu:

It looks like Quarkus won both rounds (boot time and memory footprint) with a clear lead over rival SpringBoot.

This may make us wonder… Maybe it’s time to consider some real testing, experience, and try using Quarkus. We should see how it works in real life, how it fits into our business scenario, and where it’s most useful.

However, let’s not forget the drawbacks, as we saw above, some features of the JVM do not (yet/easily) work in native executable binaries. In any case, maybe it’s time to give Quarkus a chance to prove itself, especially if the cold start issue has been bothering you. How about using one or two Quarkus-equipped pods (K8s) in your environment, it will be interesting to see how it performs after a while, won’t it?