BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Kubernetes Native Java with Quarkus

Kubernetes Native Java with Quarkus

This item in japanese

Lire ce contenu en français

Key Takeaways

  • Quarkus is an industry leader in startup time and memory utilization for native and JVM-based Java applications, leading to increased application density and reduced cloud costs. 
  • Building a native executable is as easy as adding a command-line flag to the build.
  • We created Quarkus to run Java applications more efficiently instead of making a costly switch to Node.js or Golang.
  • Developers can utilize their existing Java ecosystem expertise with build tools and APIs like Jakarta EE, MicroProfile, Spring, and more, with either imperative or reactive programming styles - or both!
  • Kubernetes is a first-class deployment platform in Quarkus with support for its primitives and features.
  • Quarkus unites the benefits of rapid development seen in scripting languages, the maturity of the Java ecosystem, and the efficiency of native compilation in a single runtime.

This article is part of the article series "Native Compilation Boosts Java". You can subscribe to receive notifications about new articles in this series via RSS.

Java dominates enterprise applications. But in the cloud, Java is more expensive than some competitors. Native compilation with GraalVM makes Java in the cloud cheaper: It creates applications that start much faster and use less memory.

So native compilation raises many questions for all Java users: How does native Java change development? When should we switch to native Java? When should we not? And what framework should we use for native Java? This series will provide answers to these questions.

What is Kubernetes Native Java?

"[They] could run 3 times denser deployments without sacrificing availability and response times of service." - Lufthansa Technik

Organizations adopt Kubernetes to deliver business value quicker and more cost-effectively. Why? Because Kubernetes makes hard tasks easier: It automatically scales applications horizontally when load increases and performs rolling upgrades.

But this approach clashes with Java’s traditional model of three-tier architectures with large heap sizes, vertical scaling, and long-running processes.

Java historically achieved exceptional performance through allocating and holding onto maximal system resources, adapting to load conditions on-demand over time.

Developers happily embraced Java’s model of long-running, heavy, yet highly dynamic JVM to enable a rich declarative programming model while running multiple diverse applications. Lazy scanning and runtime classpath analysis brought us inversion of control and less boilerplate code – at the cost of startup time and memory usage. These downsides didn’t matter much in large monolithic applications with enormous heap sizes because you only paid the startup penalty once.

But running these traditional Java applications in Kubernetes results in many fat JVMs, all hosting a resource-hungry dynamic runtime that restarts often. And restarts waste CPU cycles by analyzing the same immutable image over and over again, arriving at the same result every time. This analysis also uses a lot of memory which the JVM holds on to for its life. But density matters in Kubernetes: The more application instances that can run, the higher the return on investment from Kubernetes.

Kubernetes Native Java means Java applications can take advantage of Kubernetes capabilities instead of resisting them – small processes that start quickly instead of memory-hungry JVMs with long start-up times.

The Motivation Behind Quarkus

"Before we introduced Quarkus, many customers had started to look at alternative stacks like Go and node.js to improve performance and efficiency. These customers were weary of selecting a new language and having to hire new developers or retrain their existing Java developers." – Arijit Mazumdar, WiPro

Organizations and developers alike have a huge investment in Java. It’s an incredibly productive language with fantastic tools and frameworks that developers already know and love. Kubernetes, however, demands a new direction. While it would be a shame to give up on the many benefits of Java, runtimes with more efficient memory and startup time, such as Node.js and Golang, challenge that Java investment.

The Quarkus team set out to have its cake and eat it, too: Merging the benefits of the feature-rich, mature Java ecosystem with the operational advantages of Kubernetes. To accomplish this, Java applications have to be more statically defined at runtime, yet still able to embrace Java’s dynamic capabilities that allow developers to iterate faster.

Organizations increase application deployment pace using the Cloud. Likewise, today’s businesses need shorter development cycles that deliver business value faster. Quarkus’ approach supports this increased iteration and deployment pace by heavily focusing on "Developer Joy" and has proven that Java can be as, if not more, productive than scripting languages.

Of course, being productive isn’t just about how fast you can code. Building a modern cloud application requires interacting with other services where wiring technologies with complex YAML slows you down. We felt Developer Joy capabilities like Dev Services and Zero Config Development, covered in more detail later, were essential for Kubernetes Native Java. For example, you write code to access a database, and Quarkus magically starts and wires everything in the background as you type.

Likewise, deploying to Kubernetes should not require reading a Kubernetes administration book and authoring hundreds of lines of YAML. With Quarkus, we believed it was important that Java is all you need to know. Add the flag "-Dquarkus.kubernetes.deploy" when you build, and Quarkus deploys your application to Kubernetes (don’t forget to log in!). If you prefer, you can use the integrated Quarkus Dev UI and trigger a deployment straight from your browser.

In short, Quarkus is built from the ground up to transform Java into the ideal language for building native binaries and Kubernetes applications. Quarkus is Kubernetes Native Java!

Build-Time Fosters Native Compilation

"Quarkus has introduced a new paradigm that puts things upside down on the way things are run under the covers - supporting native, all reflection done at compile time and not runtime is just amazing!" – Roberto Cortez, Talkdesk (at time of quote, now employed by Red Hat)

Splitting the lifecycle of an application into two distinct phases is key to achieving small, lightweight processes with instant startup, as demanded by the Kubernetes Native architecture. Traditionally, Java application runtime startup conducts a series of complex, long-running, dynamic, and self-introspective steps to meet the demands of a dynamic deployment environment. These steps repeat each time an application starts.

Since a container image does not change, there is no longer a need to conduct these steps at runtime. Most dynamic startup steps can be moved to build time. The work performed at the actual start then becomes much smaller and significantly faster. Further, it’s possible to throw out code that an application doesn’t need. Tailoring the output leads to a leaner executable containing only the required code.

Quarkus brings immediate JVM efficiency benefits while organizations evaluate and prepare for Java native executables because the build-time benefits apply universally: It’s not just native compilation that profits from this approach, but traditional JVM/Hotspot as well. When there is less code that is more efficient, the output will be leaner and faster regardless of its form. That said, ahead-of-time native compilation brings the benefits to the next level: In a closed world where everything is known ahead of time, the compiler can do very fine-grained optimizations all the way down to the field, variable, and instruction set level.

However, there is a catch: For the ahead-of-time (AOT) compiler to do its job effectively, it must understand what the Java code will ultimately do. Java’s dynamic aspects that make it so highly productive also obscure the application’s behavior from the compiler and limit the degree of optimization. For example, an injection point makes the code simple and easy to evolve. But to the AOT compiler, it’s an undefined, unanswered hole in an equation.

For instance, you can easily get a native executable by marking every class as usable via reflection. But the result will likely disappoint: Memory usage and startup time are only marginally better than on a JVM. In other words, you need a fully encompassing build-time model to unleash all the benefits of native compilation. Like the one Quarkus employs!

Now, Quarkus resolves every dependency at build time and generates a complete closed-world application. The injection example described above becomes fully resolved to the AOT compiler – it now knows the class to use and can eliminate the rest of the code.

In summary, combining the optimization capabilities of GraalVM with the build-time capability of Quarkus leads to the smallest possible memory footprint and startup time. Limiting that overhead is critical to unlocking the full benefits of Kubernetes Native Java.

First-Class Native Compilation

"We are using Quarkus in JVM mode on containers but in the future, we are already planning to use Quarkus in native mode on Kubernetes and serverless environments, where native mode will be ideal" – Edouard Lamotte, Sedona

Since Quarkus and its extensions (Quarkus-optimized libraries) embrace build-time optimization, you can build any application as a low footprint native executable. Quarkus takes care of all the work! Other solutions often require more developer involvement: You might be able to compile to native, but only if your application uses an API subset that is explicitly supported or if you annotate code with GraalVM hints. Even if you follow the GraalVM rules, you often have to keep a separate JSON configuration up to date. In addition, Quarkus enables developers to easily write tests that run against the native executable, ensuring what you built runs as expected in a native executable.

We feel a platform’s broad support of native compilation is essential to enabling the effective use of the technology. For this reason, we go to great lengths to make adopting native compilation as easy as possible. Additionally, we ensure that every extension shipped in Quarkus supports it. We strongly recommend that all "Quarkiverse" extensions (our repository for community-contributed extensions) support native compilation as well. Not all Java libraries are compatible with GraalVM’s native compilation. However, since Quarkus has a vast number of extensions (nearly 500 and counting!), there is a good chance any use case you can think of is covered. If not, writing a Quarkus extension for your use case isn’t difficult.

Developer Joy

"Quarkus has been driving some pretty amazing innovation in developer experience. Their dev mode console and Testcontainer support is next level." – James Ward, Java Champion

As mentioned earlier, Kubernetes Native Java isn’t just about running lean. It’s just as essential to enhance and extend the productivity of Java and expand its productivity advantage over other natively compiled languages, like Golang. Every tool, framework, and runtime claims it increases developer productivity. But Quarkus takes it to the next level by delivering Developer Joy. What do we mean by that?

Quarkus strives to provide a fun, enjoyable developer experience that keeps Java developers engaged. Quarkus aims to keep every developer focused on active coding because we know how counterproductive it is to stop, run a bunch of tools, and wait for something to finish. Quarkus is so productive that developers feel encouraged to try "what if" scenarios – something that deadlines might not have allowed for in the past! Consider the following Developer Joy features:

  • Live Coding. All code changes are live, even when changing dependencies or configuration files, without giving up strong typing. Each time the application is accessed, Quarkus evaluates code changes, regenerates necessary bytecode, and reloads the application in the background to return updated results, typically in half a second or less. Live Coding works with virtually all code changes and refactorings, no matter the complexity, with no IDE plugins or special tooling required.
  • Dev Services. Quarkus uses Testcontainers to automatically instantiate and configure services during development and testing, such as databases, caches, and Kafka. Simply add an extension and start coding as the container automatically downloads and boots in the background!
  • Zero-Config Development. Convention over configuration means that no configuration is required for Dev Services. The Kubernetes deployment YAML is automatically generated specifically for any target distribution, whether it’s Minishift, OpenShift, or generic Kubernetes.
  • Continuous testing. Automatically run tests every time a file is saved. Run all tests, failed tests, or only tests relevant to code changes (which Quarkus figures out). The following image demonstrates the results of a live coding change and its continuous testing output, making Java development as productive as a scripting language!

  • Dev UI. Visualize and utilize Quarkus extension features in a web browser while developing. The following image shows a sample application with Dev UI components that allow developers to update a configuration live, view CDI beans, deploy to OpenShift, open the Swagger UI, and more.

  • Command mode. Complements the Dev UI by offering similar Dev UI features from the keyboard in the terminal where Quarkus developer mode was started, like changing the log level with a single keystroke without re-starting the JVM.
  • Quarkus CLI. A command-line tool to manage a Quarkus project. The CLI can generate and build a project, manage dependencies, run tests, and more.
  • Standards and Best of Breed framework support. Quarkus offers support for standards like MicroProfile and some Jakarta EE specifications like JAX-RS, JPA, and JTA, as well as Apache Camel, Hibernate, Kafka, Spring Compatibility APIs, and dozens more.
  • Kotlin Support. In addition to Java, Quarkus supports a popular alternative language for the JVM – Kotlin.
  • IDE Integration. Quarkus plugins are available for IDEs like IntelliJ, Visual Studio Code, and Eclipse with features like enhanced code completion. However, Quarkus does not require an IDE. Developer Joy works with any editor, even vi and Notepad!

When combined, these capabilities accelerate a development team, allowing them to focus on what they do best. These benefits speed the delivery of a project, meet customer requirements quicker, and finally, boost application quality with more time spent on business logic than on plumbing.

Imperative and Reactive

"[Quarkus is] able to handle reactive or imperative naturally" – LogicDrop

Runtime performance matters. The reactive programming model scales much more efficiently and with improved responsiveness due to lower overall resource utilization. But achieving maximum runtime performance shouldn’t require a rewrite and shouldn’t come at a loss of ease-of-use and developer flexibility. Hence, Quarkus is built on a unified blocking/reactive I/O stack based on the Eclipse Vert.x toolkit.

Quarkus implements smart routing, making the combination of imperative and reactive APIs a natural development experience.

When an application uses Quarkus reactive APIs, code runs on I/O threads. This reduces thread context switching and maximizes throughput while minimizing resource consumption.

When developing with imperative APIs, Quarkus dispatches work on worker threads and then switches work back to the I/O thread when done.

Develop with blocking, imperative APIs, or asynchronous, reactive APIs, even in the same application (or same class)! Quarkus seamlessly and naturally allows both approaches side-by-side using Quarkus smart routing. Developers don’t need to make the choice up-front before writing a line of code.

Goodbye Boilerplate!

"[I saw] 30 to 40% better developer productivity ..." – Christos Sotiriou, Vodafone Greece

The Quarkus community strongly believes that everything you type in a Quarkus project should be super concise, easy to understand, and not wear out your cursor keys! Quarkus includes many popular APIs, enhancing them to take advantage of Quarkus’ build-time knowledge.

For example, Quarkus enhanced Hibernate with "Panache", a set of API extensions that make data access as natural as modern Java. This code fragment is all you need to express a JPA Entity. No getters, no setters, no boilerplate (unless you like them, then, by all means, keep them)!

@Entity
public class Person extends PanacheEntity {
    public String name;
    public LocalDate birth;
    public Status status;
}

Querying a database is now trivial as well:

List<Person> allPersons = Person.listAll();

Quarkus extensions can detect the presence of other extensions and integrate with them, reducing the amount of code you need to write, favoring what we feel are the best patterns and practices.

For example, if the "Panache" code snippet belongs to an application that includes the datasource and health check extensions, the datasource extension will automatically define a database health check for the application and a Kubernetes Readiness Probe. As a result, Kubernetes will not send traffic to the application container when the database is unavailable.

Even further, if the same application includes a metrics extension, the datasource extension will  automatically expose metrics for monitoring tools like Prometheus.

Kubernetes as a First-Class Platform

"Does this mean I can go back to writing code since I don’t have to deal with [Kubernetes] service configurations anymore because Quarkus does it for me?" -- DevOps Engineer, LogicDrop

Quarkus is inherently Kubernetes Native, achieving runtime efficiency from both Java bytecode and a native executable. Additionally, Quarkus supports Kubernetes as a first-class platform, providing the following extensions and capabilities:

  • Configuration. Quarkus can utilize the Kubernetes API to access configuration stored in a ConfigMap or Secret without mounting it to the pod’s filesystem.
  • Application Health. Exposes application health to Kubernetes health probes for potential traffic redirection and pod restarts using MicroProfile Health.
  • Kubernetes Client. Wraps a Java object model around Kubernetes objects and offers a Java API for manipulating those objects.
  • Service Discovery and load balancing. Applications can utilize DNS within a Kubernetes Cluster for service discovery with Kubernetes round-robin load balancing. Or they can use the Quarkus’ Stork client-side load balancing framework for a more sophisticated or customized load-balancing algorithm.
  • Simplified Kubernetes and Knative configuration and deployment. Quarkus generates the YAML required for deploying to Kubernetes or Knative, with the latter adding serverless capabilities to Kubernetes. YAML generation can be customized using Quarkus properties.
  • Observability. Gain insight into running applications using Micrometer or MicroProfile Metrics and follow trace requests across multiple services using MicroProfile OpenTracing or OpenTelemetry.
  • Functions as a Service (FaaS). Quarkus offers Funqy as an API for developing portable functions that run on Knative with Knative events and in FaaS environments like AWS Lambda, Azure Functions, and Google Cloud Functions.
  • Remote Development. Quarkus supports remote live coding out-of-the-box with no extra tooling. You can Live Code an application running in a Kubernetes Pod (or other remote environment) – no special IDE tooling required!
  • Quarkus Operator SDK. Simplifies writing Kubernetes Operators with Quarkus.

Getting Started with Quarkus

"I was able to get up and running with Quarkus very quickly. The Quarkus Guides have been a huge help as they focus on one topic and get straight to the point." – Victor Gallet - Senior Developer and Kafka Expert DECATHLON

The best way to learn more is to create your first Quarkus application. It only takes a couple of minutes to get started – it’s only four steps! Just head to the getting started guide which walks you through each step. Spring developers can come up to speed on Quarkus quickly by reading the free ebook Quarkus for Spring Developers.

After that, you might want to take advantage of our code.quarkus.io site: Just pick the extensions you want and generate your project!

Conclusion

Kubernetes Native Java is about redefining how we use Java to embrace the Kubernetes model, where decreased startup time and memory utilization reduce cost with increased resource efficiency in a shared environment. Quarkus accomplishes this by moving much of its startup processing to build time. The result is an efficient runtime that can considerably reduce cloud computing costs with significantly more resource-efficient applications. Additionally, organizations can deliver new features that directly impact the business without exceeding their current cloud computing budget.

To maximize the benefits of native compilation, Quarkus integrates closely with GraalVM. Applications are tailored to best leverage the optimizations of ahead-of-time compilation. Quarkus uses GraalVM's extension model to enhance this process and avoid additional configuration.

Quarkus delivers Developer Joy which keeps the developer engaged in addressing the business problem with features like Live Coding, Dev Services, and continuous testing. Developing with Quarkus can be so productive that Java developers can try multiple problem-solving approaches and select the one that has the most business impact.

Quarkus also considers Kubernetes a first-class platform. It delivers a collection of API frameworks that allow applications to take full advantage of what Kubernetes offers. This maximizes Kubernetes investments and accelerates time-to-market through platform integration.

Get started!

 

This article is part of the article series "Native Compilations Boosts Java". You can subscribe to receive notifications about new articles in this series via RSS.

Java dominates enterprise applications. But in the cloud, Java is more expensive than some competitors. Native compilation with GraalVM makes Java in the cloud cheaper: It creates applications that start much faster and use less memory.

So native compilation raises many questions for all Java users: How does native Java change development? When should we switch to native Java? When should we not? And what framework should we use for native Java? This series will provide answers to these questions.

About the Authors

Rate this Article

Adoption
Style

BT