How Quarkus Combines Imperative and Reactive Programming

This year we plan to seriously develop container themes, Cloud-Native Java and Kubernetes . A logical continuation of these topics will be the story about the Quarkus framework, already considered on Habré. Today's article focuses not so much on the “subatomic ultrafast Java device” as the perspectives that Quarkus brings to Enterprise. (By the way, register and go to our webinar “ This is Quarkus - Kubernetes native Java framework ”, which will be held on May 27. We will show how to start “from scratch” or transfer ready-made solutions)



Java and the JVM are still extremely popular, but when working with serverless technologies and cloud-oriented microservices, Java and other languages ​​for the JVM are used less and less, because they take up too much memory space and load too slowly, which is why they are not suitable for use with short-lived containers. Fortunately, this situation is currently starting to change thanks to Quarkus.

Ultrafast subatomic Java has reached a new level!


42 releases, 8 months of community work and 177 amazing developers - the result of all was the release in November 2019 of Quarkus 1.0 , a release that marks an important milestone in the development of the project and offers a lot of cool features and capabilities (more about them can be found in the announcement ) .

Today we will tell how Quarkus combines imperative and reactive programming models based on a single reactive core. We'll start with a brief digression into history and then take a closer look at what the dual core of Quarkus reacts to and how Java developers can take advantage of these.

Mikroservisy , event-driven architecture and serverless-functions - all this today, as they say, is on the rise. Recently, the creation of cloud-based architectures has become much simpler and more affordable, but problems remain - especially for Java developers. For example, in the case of serverless-functions and microservices, there is an urgent need to reduce startup time, reduce memory consumption and make their development more convenient and enjoyable. Java in recent years has made several improvements, such as ergonomics functionality modified for containers, and so on. However, getting Java to work in the container is still not easy. Therefore, we will start by looking at some of the intrinsic complexities of Java, which are especially acute when developing container-oriented Java applications.

First, let's turn to the story.


Streams and containers


Starting with version 8u131, Java has more or less supported containers due to improvements in ergonomics functionality. In particular, the JVM now knows how many processor cores it runs on, and can properly configure thread pools — typically fork / join pools. Of course, this is great, but let's say we have a traditional web application that uses HTTP servlets and runs on Tomcat, Jetty, and so on. As a result, this application will give each request a separate stream and allow it to block this stream while waiting for I / O operations, for example, when accessing a database, files or other services. That is, the size of such an application does not depend on the number of available cores, but on the number of simultaneous requests. In addition, this means that quotas or limits in Kubernetes by the number of cores will not particularly help here,and the deal will end in trotting.

Running out of memory


Streams are memory. And intra-container memory restrictions are by no means a panacea. Just start to increase the number of applications and threads, and sooner or later you will encounter a critical increase in the switching frequency and, as a result, with degradation of performance. In addition, if the application uses traditional microservice frameworks or connects to the database, or uses caching, or somehow additionally consumes memory, you absolutely need a tool that allows you to look inside the JVM and see how it manages the memory, and not kill JVM itself (e.g. XX: + UseCGroupMemoryLimitForHeap). And even despite the fact that, starting with Java 9, the JVM has learned to accept cgroups and adapt accordingly, backing up and managing memory remains a rather complicated affair.

Quotas and limits


Java 11 introduced support for CPU quotas (like PreferContainerQuotaForCPUCount). Kubernetes also offers limit and quota support. Yes, all this makes sense, but if the application again goes beyond the allocated quota, we again come to the conclusion that the size - as is the case with traditional Java applications - is determined by the number of cores and with the allocation of a separate thread for each request, then there is little sense in all this.
In addition, if you use quotas and limits or functions of horizontal (scale-out) scaling of the platform that underlies Kubernetes, the problem also does not solve itself. We simply spend more resources on solving the original problem or, as a result, we end up using resources. And if it is a highly loaded system in a public, public cloud, we will almost certainly begin to use more resources than it really needs.

And what to do with all this?


If in a simple way, then use asynchronous and non-blocking input-output libraries and frameworks like Netty, Vert.x or Akka. They are much better suited to work in containers due to their reactive nature. Thanks to non-blocking I / O, the same thread can process several simultaneous requests at once. While one request is waiting for I / O results, its processing thread is freed up and taken for another request. And when the I / O results finally arrive, the processing of the first request continues. By alternating request processing within the same stream, you can reduce the total number of threads and reduce the resource consumption for processing requests.

With non-blocking I / O, the number of cores becomes a key parameter, because it determines the number of I / O threads that can run in parallel. When used correctly, this allows you to effectively distribute the load between the cores and cope with higher loads with less resources.

How, and is that all?


No, there is one more thing. Reactive programming helps make better use of resources, but also comes at a price. In particular, the code will have to be rewritten according to the principles of non-blocking and avoid blocking input-output streams. And this is a completely different model of development and implementation. And although there are a lot of useful libraries, this is still a cardinal change in the usual way of thinking.

First, you need to learn how to write code that runs asynchronously. As soon as you start using non-blocking I / O, you need to explicitly prescribe what should happen when you receive a response to the request. Just blocking and waiting will fail. In return, you can pass callbacks, use reactive programming, or continuation. But that's not all: to use non-blocking I / O, you need both non-blocking servers and clients, and preferably everywhere. In the case of HTTP, everything is simple, but there is also a database, and file systems, and much more.

Although total end-to-end reactivity gives maximum efficiency, such a shift can be difficult to digest in practice. Therefore, the ability to combine reactive and imperative code becomes a necessary condition in order to:

  1. Effectively use resources in the most loaded areas of the software system;
  2. Use a simpler style code in its other parts.

Introducing Quarkus


Actually, this is the essence of Quarkus - to combine reactive and imperative models within one runtime environment.

Quarkus is based on Vert.x and Netty, on top of which a number of reactive frameworks and extensions are used to help the developer. Quarkus is designed to build not only HTTP microservices, but also event-driven architectures. Due to its reactive nature, it works very efficiently with messaging systems (Apache Kafka, AMQP, etc.).

The trick is how to use the same jet engine for both imperative and reactive code.



Quarkus does it brilliantly. The choice between imperative and reactive is obvious - to use both the one and the other reactive core. And with what it helps a lot, it’s with fast non-blocking code that processes almost everything that passes through the event loop thread (event-loop thread, aka IO thread). But if you have classic REST or client-side applications, Quarkus is ready for an imperative programming model. For example, HTTP support in Quarkus is based on the use of a non-blocking and jet engine (Eclipse Vert.x and Netty). All HTTP requests received by your application first pass through the event loop (IO Thread), and then they are sent to the part of the code that manages the requests.Depending on the destination, the request control code can be called within a separate thread (the so-called worker thread, used in the case of servlets and Jax-RS) or use the original input-output stream (reactive route).



Non-blocking clients working on top of the Vert.x engine are used for connectors of messaging systems. Therefore, you can efficiently send, receive, and process messages from messaging middleware class systems.

The site Quarkus.io collected some good guidelines to help you get started with Quarkus:


In addition, we have prepared online practical lessons to familiarize yourself with various aspects of reactive programming, moreover, just a browser is enough to complete them, no IDE is required for this, and a computer is not required. Find these lessons here .

Useful resources




10 Quarkus video tutorials to get comfortable with the topic


As Quarkus.io writes on the site , Quarkus is a Kubernetes- oriented Java stack, sharpened by GraalVM and OpenJDK HotSpot and compiled from the best Java libraries and standards.

To help you understand the topic, we have selected 10 video tutorials that cover various aspects of Quarkus and examples of its use:

1. Introducing Quarkus: The Next Generation Java Framework for Kubernetes


Authors: Thomas Qvarnstrom and Jason Greene
The goal of the Quarkus project is to create a Java platform for Kubernetes and serverless environments, as well as combine reactive and imperative programming models into a single runtime so that developers could flexibly vary the approach when working with a wide range of distributed application architectures. Learn more from the introductory lecture below.



2. Quarkus: ultrafast subatomic Java


Author: Burr Sutter A
video tutorial from the DevNation Live online lecture demonstrates how to use Quarkus to optimize enterprise Java applications, APIs, microservices, and serverless features in Kubernetes / OpenShift, making them much smaller, faster, and more scalable.



3. Quarkus and GraalVM: we accelerate Hibernate to super speeds and squeeze to subatomic sizes


Author: Sanne Grinovero
From the presentation you will learn how Quarkus appeared, how it works and how you can make complex libraries like Hibernate ORM compatible with GraalVM native-images.



4. Learning to develop serverless applications


Posted by Marthen Luther
The video below shows how to create a simple Java application using Quarkus and deploy it as a serverless application on Knative.



5. Quarkus: code with pleasure


Posted by Edson Yanaga
Wideguide to create your first Quarkus project to understand why Quarkus is winning the hearts of developers.



6. Java and containers - what will be their common future


Posted by Mark Little
This presentation introduces the history of Java and explains why Quarkus is the future of Java.



7. Quarkus: ultrafast subatomic Java


Author: Dimitris Andreadis Dimitris Andreadis
An overview of Quarkus’s acclaimed developers: simplicity, ultra-fast speeds, top libraries and standards.



8. Quarkus and subatomic reactive systems


Posted by Clement Escoffier
Through integration with GraalVM, Quarkus provides super-fast development experience and a subatomic runtime. The author talks about the reactive side of Quarkus and how to use it when creating reactive applications and streaming applications.



9. Quarkus and rapid application development at Eclipse MicroProfile


Posted by John Clingan By
combining Eclipse MicroProfile and Quarkus, developers can create fully functional MicroProfile container applications that run in a few tens of milliseconds. The video details how to encode the MicroProfile container application for deployment on the Kubernetes platform.



10. Java, Turbo Version


Posted by Marcus Biel
The author shows how to use Quarkus to create super-small and super-fast Java containers for a real breakthrough, especially in serverless environments.


All Articles