Java has been in a bit of an awkward spot since containers took off a few years ago. In the world of Kubernetes, microservices, and serverless, it has been getting harder and harder to ignore that Java applications are, by today’s standards, overweight. Well, until now. In this article, I explore the basics of Quarkus, a brand new Kubernetes Native Java framework built to specifically address Java’s weight problem.
Java of Yore
For years, many of us have looked the other way when confronted with the bloatiness of Java. Who cares if my server side app needed hundreds of megabytes worth of class files, created gigabytes worth of runtime memory footprint, and took up to a minute (maybe five) to start up. I definitely didn’t dare fat shame Java as long as, after the slow start of my obese deployment, my application instance could then run reliably on a powerful piece of hardware or VM for months, if not years, serving hundreds of requests concurrently. Not to mention, as a language, Java gave me pretty much everything I needed – a “write once, run (almost) anywhere” platform in the JVM, type safety, OOP support, and unrivaled set of options in tooling and libraries – to maintain my software for a long time using a large team of professionals with varying levels of skills. Further, with enterprise grade application servers (e.g. EAP, Weblogic, Tomcat), you also had a very resilient and feature rich platform for your Java web applications. Your applications simply needed to comply with JavaEE standards around describing how to deploy your application (think web.xml). Any JavaEE compliant application server would then take care of operational concerns like security, logging, connecting to databases/queues, and scaling. It’s no surprise that for years Java has dominated the programming language landscape as the de facto standard for the enterprise.
Kubernetes: The New Application Server
It has been observed before, by more people than one, that Kubernetes is the new application server. Containers and Kubernetes have taken the “write once run anywhere” paradigm of the JVM and extended it to pretty much all other programming languages. Now applications written in any language can leverage Kubernetes for operational concerns and decouple themselves from runtime infrastructure. The applications just have to be delivered in compliant Linux containers. Now developers can code up their applications in their favorite programming language and count on Kubernetes to handle operational concerns like logging, scaling, healing, and networking. Add in Istio and you even have out of box fault tolerance and application level metrics without a single line of application code.
So, today we find ourselves in a tech landscape that overwhelmingly prefers horizontal scaling of automated cattle over vertical scaling of manually cared for pets. Microservices and serverless/FaaS applications have become all the rage and both benefit greatly from low memory footprint and blazing fast startup times. So, it has become increasingly harder to ignore that my Java container images are quite a bit larger in size as well as memory footprint, and they take quite a bit longer to start up, especially when compared to a language like Golang. Modern cloud native frameworks like Spring Boot or Dropwizard have helped, but startup times are still AT LEAST ten seconds or more, and runtime memory footprint is AT LEAST in hundreds of megabytes.
Quarkus aims to tackle the bloatiness problem of Java head on. Marketed as Supersonic Subatomic Java, Quarkus leverages GraalVM and HotSpot to provide developers with a framework to create applications from Java code with fast boot time and low RSS memory. The following graphic from quarkus.io does a good job illustrating the benefits. Notice the drastic difference in both RSS memory and boot time between Quarkus native and “the traditional cloud native stack”.
OpenJDK and GraalVM
As evident from the graphic above, Quarkus has two modes – JVM and native. The native mode uses GraalVM to create a standalone executable that doesn’t run in a Java VM. Obviously, the greatest efficiency gains come from running a Quarkus application in its native mode. However, not every JVM feature works in native mode, and the most notorious of these lost features is reflection. This can be a huge problem as a great number of frameworks and libraries that Java developers have depended on for every day development rely heavily on reflection. GraalVM works around this by allowing classes to be registered for reflection at compile time. While this process can be cumbersome when working directly with GraalVM, Quarkus streamlines the registration process by detecting and auto-registering as many of your code’s reflection candidates as possible.
While Quarkus has done a pretty good job with auto registering most reflection candidates you are likely to have, you might still run into instances where you have to explicitly register some of your classes using Quarkus’s RegisterForReflection annotation. This might become more trouble than worth in some projects. For this reason as well as just general flexibility, Quarkus also offers the JVM mode. In JVM mode, Quarkus apps are packaged as JAR files and run on the OpenJDK HotSpot JVM.
Show me the Code!
So having set the stage, let’s look at some code. To get started with Quarkus, I put together a JAX-RS application following the excellent “Getting Started” guides from Quarkus. See my repo for the application code. The application is a simple service that can be used to store, update, retrieve, and delete arbitrary text values. I mostly just followed the guide as I wrote my code. I built the application out in the following stages.
In this stage, I created the core application with all the API endpoints. I started by generating an app skeleton using the
quarkus-maven-pluginand adding the
resteasy-jackson extension for JSON support.
mvn io.quarkus:quarkus-maven-plugin:1.2.0.Final:create \
mvn quarkus:add-extension -Dextensions="resteasy-jackson"
Some changes I made was getting rid of
.dockerignore and the
Dockerfile examples generated by the create task of the
quarkus-maven-plugin. Instead I prefer to use a multi-stage Dockerfile (see JVM and Native) to keep my build concerns in one file. After this it was just adding my application code as captured in this tag (or commit).
Metrics and Healthchecks
Metrics and health checks are crucial in creating twelve-factor applications. Quarkus, leveraging Microprofile, makes adding these in very straightforward.
mvn quarkus:add-extension -Dextensions="metrics"
See this tag (and commit) for the metrics I added for the application. Essentially my application collects timing metrics for all its exposed API endpoints. It also has a gauge on the value store’s size. Metrics are published at the
/metrics endpoint, which contains
application metrics. Each one of those subgroups also has its own endpoint (e.g.
mvn quarkus:add-extension -Dextensions="health"
Similarly, see this tag (and commit) for the healthchecks. I added a liveness check and a readiness check. The
/health endpoint can be accessed for all healthchecks aggregated in one. However, you typically separate these into liveness and readiness probes. For this reason,
/health/ready endpoints are also automatically provided.
The core app I put together in the first stage uses an in memory storage service. This means the storage is local to each instance of the application and gets wiped when that instance goes down. To build an actual stateless application that can be scaled up and have persistent storage, let’s offload the application state to a MySQL database.
mvn quarkus:add-extension -Dextensions="hibernate-orm,jdbc-mysql"
- Having my resource class depend on the
StorageServiceinterface abstraction, I have to make zero code changes there to switch to persistent mode.
- To be able to pick my storage service implementation at runtime I introduce three things: a
sample.storage.typeproperty, a producer class to create the right bean based on the property, and a
ConfiguredStorage) for my resource class to specify it intends to use the bean produced by the producer class.
- I leverage the
application.propertiespattern to use in-memory storage as the default storage type. I intend to use environment variables to override these properties to switch over to persistent storage. There is one gotcha here however. Quarkus does much of its configuration and bootstrap at build time. Most properties will then be read and set during the build time step. To change them, make sure to repackage your application. In my
quarkus.datasource.health.enabledcannot be overridden at runtime. Good news is that the rest can.
And that’s it
I have a couple more commits around adding native build support and documentation. However, the application is ready to go. My repo
README.md does a good job of walking through the details of building and running this application locally. You can use the following steps as a reference for running the application on Openshift or Code Ready Containers.
# Create a new project
oc new-project samples
# Standup MySQL
oc new-app --name=valuesdb mysql-ephemeral \
-p DATABASE_SERVICE_NAME=valuesdb \
-p MYSQL_ROOT_PASSWORD=password \
-p MYSQL_USER=valsuser \
-p MYSQL_PASSWORD=password \
# Create the application schema in MySQL
oc rsh valuesdb-1-[pod_id] bash -c "mysql -uvalsuser -ppassword valsdb"
mysql> CREATE TABLE vals (
> id BIGINT AUTO_INCREMENT PRIMARY KEY,
> value VARCHAR(255) NOT NULL,
> date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
> last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
# Create API application from Github repo
oc new-app --name valuesapi https://github.com/saharsh-samples/sample-quarkus-app
# Expose a route
oc expose svc/valuesapi && oc get routes
# To use persistent storage, first create a secret containing DB configuration
oc create secret generic valuesapi-properties \
# Turn the fields of the secret into environment variables for the API app
oc set env dc/valuesapi --from=secret/valuesapi-properties
# Add liveness and readiness probes
oc set probe dc/valuesapi --liveness --get-url=http://:8080/health/live
oc set probe dc/valuesapi --readiness --get-url=http://:8080/health/ready
Quarkus is an exciting new development in the Java ecosystem. I will make sure to share more articles and code as I explore Quarkus in relation to serverless, reactive programming, and Kafka. In the meanwhile, checkout the following links to dig deeper now.