Although Quarkus is relatively new, it's attracting a lot of attention. The project was created by the open source community and consists of a Kubernetes-native Java stack tailored for OpenJDK Hotspot and GraalVM, crafted from the best-of-breed Java libraries and standards. Hence, it's an excellent option for new cloud native technologies like serverless and microservices.
What Will I Learn about Quarkus in This Snippet?
- Its benefits;
- How it works under the hood;
- How to bootstrap your first Quarkus application;
- How Quarkus performs compared to the Spring Boot Framework.
Quarkus improves productivity and accelerates the release of new products to the market. In comparison to Spring Boot, Quarkus reduces the required steps to update an application from six to two. Using Quarkus, a developer who makes changes to and tests the source code only has to go through two steps: changing the code and saving it.
In Spring Boot, the developer needs to go through six steps: making the changes to the project, stopping the service, testing the changes, starting the application, and testing it again. Quarkus's reduction of these steps also makes the compilation easier and more efficient.
How does it work? The decrease in the number of processes is possible due to live coding. Application updates using live coding are seen immediately.
Quarkus also unifies imperative and reactive programming to give developers the option to combine them freely. By allowing fewer projects and source files, professionals can reduce the time needed for the maintenance of the system, as well as the number of projects they need to manage.
One of the main benefits of Quarkus is the saving of resources, as the IDC Validation Report attests. Both Quarkus JVM and Quarkus native require less memory consumption, in addition to faster startup times, leading to a higher deployment density of Kubernetes pods.
Quarkus also has a lower operating cost in comparison to Spring Boot. Operating with Quarkus in native mode - using GraalVM to create a standalone optimized executable that does not operate on a traditional Java Virtual Machine (JVM) - can realize improvements up to 64%, while operating in a JVM reaches 37%.
Traditionally it has been said that Java uses a lot of memory at startup, not being compatible with light applications. Quarkus demystifies this view. Its native version reduces memory usage at startup by 90%, while Quarkus JVM drops memory usage by 20%. This results in greater processing capacity for the same memory area in both the JVM and native modes, which means that more can be done with the same memory capacity.
Quarkus is also much faster at startup than Spring Boot. Quarkus Native is 12x faster, while Quarkus JVM is 2x more agile. This makes the application more responsive to changing loads and more reliable during operations with a great need for scale (for example, serverless), increasing innovation capabilities and offering an advantage over its competitors.
How It Works
Quarkus adopts the container first approach. This approach is optimized for low memory usage and fast startup times in areas like build time metadata processing and reduction in reflection usage.
Normal frameworks use build time for packaging only, and most work is given for runtime processing, such as classpath scanning. Every time the application is restarted, all of the steps are done again, increasing start and loading time. The whole process is illustrated in Figure 1.
Figure 1 - Normal application load steps. Image courtesy of Quarkus: The Black Swan of Java?
Quarkus transfers these steps to build time by using its extensions. There are extensions for many Java frameworks, such as RESTeasy, Hibernate, database drivers, and also spring features. These extensions help on loading only what is needed and avoiding unnecessary classes, methods, and lines of code that are never reached. All of the work is done once, not at each start, and not all of the bootstrap classes are loaded. This approach is illustrated in Figure 2.
Figure 2 - Quarkus application load steps. Image courtesy of Quarkus: The Black Swan of Java?
For bootstrapping a Quarkus application, there are three distinct phases: Augmentation, Static, and Runtime Init.
Augmentation is done by Build Step Processors. These processors have access to Jandex annotation information and can parse any descriptors and read annotations but should not attempt to load any application classes. Jandex is an index for classes to look things up, since Quarkus does not do annotation processing or normal class loading. The output of these build steps is recorded bytecode, using an extension of the ObjectWeb ASM project called Gizmo(ext/gizmo), which is used to actually bootstrap the application at runtime.
Static init is done for frameworks that can boot state directly written to the image, and so the boot code does not need to be executed when the image is started.
Runtime init will be run on native executable boot. In general, as little code as possible should be executed in this phase, and it should be restricted to code that needs to open ports etc.
Even though recommended, Graal SDK is not necessary to run a Quarkus application. A JDK JIT can be used. To change the way a Hotspot JDK normally works, Quarkus takes advantage of its core, Jandex, Gizmo, and extensions. Those tools are illustrated in Figure 3.
Figure 3 - Quarkus extensions and tools. Image courtesy of Quarkus: The Black Swan of Java?
Bootstrapping Your First Quarkus Application
- Go to https://code.quarkus.io/
- Add the extensions you will need: in this case, I only added the RESTEasy JAX-RS extension, as illustrated in Figure 4.
Figure 4 - Quarkus bootstrap screen.
- Download and open as a Gradle project (very similar to Spring Initializr).
- Create a simple endpoint, as illustrated in Figure 5.
Figure 5 - Hello endpoint.
Quarkus JVM Mode
- To spin up Quarkus in JVM mode, just click on the task quarkusDev or run:
- ./gradlew quarkusDev
- In this mode, you will get access to live reloading. Done! The application is already running, as illustrated in Figure 6.
Figure 6 - Hello world.
- Quarkus Native Mode
- Starting Quarkus in native mode is a bit more complex. If you don't have GraalVM installed, you must use Java 11.
- Execute the command:
- ./gradlew build -Dquarkus.package.type=native -Dquarkus.native.container-build=true
- After this is done, run the following command:
- java -jar build/quarkus-ac-snippet-1.0.0-SNAPSHOT-native-image-source-jar/quarkus-ac-snippet-1.0.0-SNAPSHOT-runner.jar
- And done! The application is running.
Start Up Time: Spring Boot vs Quarkus JVM vs Quarkus Native
The figures below illustrate the startup time for Spring Boot, Quarkus JVM, and Quarkus native applications. The difference may increase as more endpoints and dependencies are added to the project. The following applications only consider the amount of time to start one endpoint, as illustrated in Figures 7, 8, and 9.
Spring Boot: 2.117s average
Figure 7 - Spring Boot startup console.
Quarkus JVM: 1.573s average
Figure 8 - Quarkus JVM startup console.
Quarkus Native: 0.893s
Figure 9 - Quarkus native startup console.
Today we covered the basics of Quarkus, and we also reviewed a quick comparison between Spring Boot, Quarkus JVM, and Quarkus native startup times. Of course, startup time and memory efficiency are not the only metrics we have to look for when designing a software, but when it comes to serverless environments, these are must haves. Now you can embrace a new world without having to change your technology!
(Co-written by Matheus Cordeiro)
Lucas Campos is a Senior Software Engineer at Avenue Code.