Micronaut vs Quarkus — what’s the difference?

Michał Chmielarz
SoftwareMill Tech Blog
16 min readJan 22, 2021

--

When you choose a framework or a toolkit for a new Java project and look around, you spot plenty of options out there. I will not be too wrong in saying that the most popular one right now is Spring Boot from Pivotal.

In this article, the first one in the mini-series, I would like to shed some light on two relatively new options.

“New” kids on the block

As you may have guessed, the story is about two frameworks: Micronaut and Quarkus. I want to go through a broad spectrum of their features to see what they offer.

Both aspire to be modern frameworks, having support corresponding to the current challenges of writing JVM applications.

Shortly about both frameworks

Why were they created? What issues do they address?

The most important aspects presented by both frameworks are lower memory consumption and rapid startup time. They achieve this by using compile-time dependency injection and aspect-oriented programming. There are two major advantages of this: smaller memory footprint as we don’t need to store reflection metadata and faster runtime since the compilation process takes care of dependencies injection. Both these advantages make a difference, especially when running services in the cloud — we can buy a cheaper machine running our apps.

The possibility of creating native images using GraalVM out of the box is the next feature supporting the rapid startup of applications developed with the frameworks. That feature boost development and deployment of cloud-native applications and serverless deployments — a thing that cannot be overestimated nowadays.

Another concern addressed is the rapid development of microservices with a strong focus on cloud deployment. Developing microservices and deploying them on a cloud platform gained momentum some time ago. The frameworks provide extensions making the task easier. However, with both, we can still create a monolith application.

Who’s backing the project?

Before choosing a framework, the critical thing is to know who is backing a given tool, whether it is alive, and how big its community is. How are both going?

For Micronaut, the key maintainer and supporter is Object Computing, Inc. You may know it from delivering the Grails framework.

Quarkus, on the other hand, is developed and backed by RedHat, Inc., which is a well-known company in the programming world.

Quarkus depends heavily on the Vert.x toolkit and MicroProfile project. The Eclipse Foundation maintains both. Micronaut, on the other hand, is more like Spring. Its development is based on the experience gained by Object Computing Inc. on the basis of their work with Spring and Grails frameworks.

And yes, both are alive; we can see a new version released a couple of times per year for both. By the way — licensing in both cases uses Apache Licence 2.0.

The other question is: When did they emerge?

Looking at the history of Micronaut on GitHub, the initial project setup took place in May 2017. Since this date, we’ve had 66 releases till the 21st of January 2021. The number of commits is reaching 10k.

What does it look like for Quarkus? The history began in June 2018. Since then, we’ve had 100 releases till the 21st of January 2021. And we’ve got more than 18k commits.

I was wondering how to measure their popularity. The quick idea for checking this is looking at stars on GH and the Google Trends site. Micronaut has 4.6k stars at the writing time, while Quarkus is slightly better with 6.8k stars. The Google Trends graphs present Quarkus as the leading one, but the gap isn’t significant.

To put this into perspective, let’s look at the Spring Boot stats. It started in October 2012 (date of the first commit) and has 30k commits already with 181 releases. The number of GitHub stars is 53.1k. And if we compare Google Trends for the three frameworks, Micronaut and Quarkus are dimmed with Spring Boot results. That’s the effect of being on the market for 5–6 years longer.

Tooling

Now, how can we start working with these? We have various ways of bootstrapping a project using each framework.

Launch pages

First, we can use dedicated pages, similar to Spring Initializr, to create a project:

  1. https://micronaut.io/launch/
  2. https://code.quarkus.io/

There, we can select a framework version, implementation language, and what components we’d like to use.

IDEs

We can create a project in an IDE too. IntelliJ IDEA is the IDE I’m using daily, so I’ve checked its support at first. The Ultimate version provides built-in support for both frameworks. The packages allow for creating a wizard project and provide decent support for writing the code, navigating through it, and running applications using IDEA.

When it comes to the community version of IntelliJ IDEA, there is no support for Micronaut. Quarkus, on the other hand, has two dedicated plugins available, the first being Quarkus Run Configs, which supports building and debugging already existing applications and is provided by a private person. The second, the official one, Quarkus Tools, provided by RedHat supports project bootstrapping with code assistance.

Both frameworks have support in other IDEs as well. Micronaut support in Eclipse rests basically on Gradle and Maven plugins. We have a dedicated extension for the Visual Studio Code, providing a project wizard, installing Micronaut CLI, code assist, and building native images with GraalVM.

Quarkus add-on to Eclipse IDE is a part of the JBoss Tools plugin and provides project creation support, running configurations, debugging options, and code assistance. For the VS Code, the Quarkus extension adds the generation of a project, extensions management, and debugging options.

Build tools support

Both projects have good support in terms of integration with build tools.

For Micronaut, when we work with the Gradle build tool, we can use one of two plugins available, depending on what we need. Both plugins have great documentation.

Micronaut Library is dedicated to creating extensions of the framework. It applies BOM and Java plugin to our project, together with annotation processing and a dedicated DSL for its configuration.

The second plugin, the Micronaut Application plugin, backs us up working on the application. It extends the first plugin, adding GraalVM and Docker operations support and running our app in the dev mode.

There’s also the Maven plugin. Unlike the Gradle support, there is only one plugin supporting application development and its distribution artifacts. Its features are running the application in the dev mode, packaging it to various formats, and supporting Docker operations (generation of Dockerfiles and image deployment).

It looks like the main focus is put on Gradle plugins, having just-enough support for Maven provided.

For Quarkus, the situation with build tools support is slightly different. Here, Maven looks like the primary tool. What does the plugin offer? We can scaffold a project, deal with extensions, run the app in the development mode (locally or remotely), build a native executable, or run native tests.

Support for Gradle tooling has “preview” status. What’s surprising is the recommended way of scaffolding a Gradle project — by using the Maven plugin! What are the features of the Gradle plugin? We can list and add framework extensions, run the application in the development mode (locally or remotely!) with an optional debug mode, run native tests, and build a native image or uber-jar.

It looks like both plugins provide the same features, but the Gradle version has preview status.

Command Line Interface

What’s interesting is the fact that both frameworks offer a command-line interface as well.

Micronaut provides a few ways of installing its CLI — we can use SDKman, Homebrew, MacPorts, Chocolatey, or binary file when running on Windows. The functionality it offers is the creation of an app skeleton using various templates.

Quarkus support of the native CLI is coming soon, and the current option (at the time of writing this) is to use the quarkus-cli jar file. The offering is slightly better in terms of features: we can scaffold a project, manage its extensions, and run it in the dev mode.

Types of applications

While both frameworks are general-purpose, they were designed to support the rapid creation of microservices and their cloud deployment. That’s not surprising when we look at recent trends in the software development world. We can create REST and message-oriented services. There is support for serverless functions as well.

However, we can still create traditional autonomous web applications. Micronaut and Quarkus can render pages using template engines.

Next, if you’d like to create a command-line application, both frameworks provide dedicated extensions for it. We can provide Android applications and IoT deployments with them as well.

Check the frameworks’ GitHub to see some examples of applications created with Micronaut or Quarkus.

The crucial feature of both frameworks is the possibility of creating native versions of our applications. By utilizing GraalVM, we can get applications starting quicker and consuming less memory than usual Java applications.

Languages

What about the programming languages? As you may guess, we’re not bound to Java only. Micronaut supports Java, Groovy, and Kotlin. Quarkus, on the other hand, has Java with Kotlin and Scala in preview status.

Technology

Let’s now see what we can do in the code.

Configuration management

Quarkus configuration management uses the SmallRye Config extension. Configuration sources supported by it include files (properties, HOCON, or YAML), Zookeeper, file system, or Java’s Properties and Map classes.

We can override settings in several ways:

  • with system properties,
  • using environment variables,
  • by specifying an external file containing overridden properties,
  • and by reading configuration from the cloud.

We can provide custom property sources as well.

In terms of configuration profiles, the framework defines three default ones (test, dev, and prod), but we can provide custom ones as well.

The naming of the configuration properties allows us to prefix them with the name of a profile. Thus, we can store the whole configuration for all required profiles in a single place.

As you may expect, settings can be overridden in various ways like system properties, environment variables, a .env file, or application properties files placed under the config folder.

Looking at the features from the code point of view, we can access the configuration using ConfigProvider.getConfig or inject Config instance, or inject specific settings directly. There is also a possibility to wrap a given subset of corresponding values using a class or interface approach.

The framework offers property expressions, custom converters, fallbacks on missing properties, logging, and hiding secrets.

For a Quarkus application, we can generate an example configuration file containing all properties for the installed extensions. That could be quite useful when we’d like to see what application parameters we can change. We can also search through all of them, using the dedicated web page.

What’s on the Micronaut side?

The framework reads configuration from various sources. The predefined ones are:

  • command-line arguments,
  • system properties,
  • environment variables,
  • files specified with themicronaut.config.files system property (from the file system or the classpath),
  • environment-specific properties,
  • and application-specific properties.

If that’s not enough, we can still implement a custom property source.

The framework holds configurations for different environments in separate files, named using the application-{environment}.properties pattern. That’s a similar solution to the one in Spring Boot.

We can access configuration values from the code by injecting them or by using the Environment bean.

Among other essential features, we have property placeholders, random values (for numbers and UUIDs) in configuration files, values injection in the code, parsing of configuration to properties classes, custom type converters, and configuration builders.

Both frameworks provide strong support for the configuration component. All the features are the things you would foresee preparing deployment. However, from the above listing, it looks like Quarkus has a slightly better offering.

Database support

Both frameworks support accessing databases using blocking and reactive implementations. We can integrate with various external systems.

Starting with Micronaut, we have quite significant support of SQL databases. Available extensions can be divided into blocking and reactive ones.

Blocking

  • JDBC: We have four types of connection pools available: Apache DBCP2, Hikari, Tomcat, or Oracle UCP, with the possibility to define multiple data sources. The framework activates a health check for configured JDBC source, and we can use the health endpoint to monitor it. We can also utilize Spring Transaction Management by adding micronaut-spring and spring-jdbc artifacts to the classpath.
  • Hibernate ORM/JPA: We can use Session or JPA API with the support of Spring Management Transactions and the possibility to define multiple persistence units. Hibernate and JPA are blocking APIs, so they run on blocking the I/O thread pool.
  • jOOQ: A library designed to write typesafe queries with fluent API generating code from a database.
  • Jdbi: A library that is written on top of JDBC, allowing for blocking access using declarative or imperative APIs.

Reactive

  • JAsync SQL: Synchronous database drivers for Postgres and MySQL written in Kotlin, based on Netty.
  • Reactive clients for MySQL and Postgres: The same clients used by the Quarkus framework provided by the Vert.x framework.
  • R2DBC clients: Reactive querying of databases with support of RxJava or Reactor types, possible to work with the repository pattern similarly to the Micronaut Data add-on.

Moreover, the framework offers the Micronaut Data toolkit, based on the repository pattern. For basic CRUD functionality, the framework provides predefined interfaces. We can extend them with other custom queries. The toolkit supports blocking, asynchronous and reactive execution of operations. With core assumptions, it is a solution similar to GORM or Spring Data. However, it uses ahead-of-time compilation to process defined queries, which means we have a thin runtime layer with no overhead from queries translation and runtime meta-model as in solutions using reflection API.

When it comes to SQL database schema management, Micronaut integrates with Flyway or Liquibase tools. Both work with JPA and GORM (with Groovy), support multiple data sources, are compatible with GraalVM, and provide a dedicated endpoint presenting information about applied migrations.

If you are writing an application using Groovy, you can use the GORM toolkit that provides components for Hibernate, MongoDB, and Neo4j.

What does integration with NoSQL solutions look like in Micronaut? At the moment of writing this, there are four of them available:

  • MongoDB with two versions of client possible (blocking and reactive);
  • Neo4j using Bolt driver;
  • Redis with support of session state and caching features;
  • Cassandra based on DataStax driver.

All of them expose health indicators (for MongoDB only with reactive-mongo enabled!), provide testing using their embeddable versions or with TestContainers.

Quarkus database connectivity is quite decent as well. We have blocking and reactive access possible:

Blocking

  • JDBC: Based on Agroal as the connection pooling implementation with the support of multiple data sources. Provides extensions for DB2, Derby, H2, MySQL/MariaDB, MS SQL Server, and Postgres, but it is possible to register others. The health check is automatically available for all the registered data sources when SmallRy Health is on the classpath. JTA transactions are possible with Narayana Transaction Manager.
  • Hibernate ORM/JPA: We can use JPA API with the support of Narayana Transaction Manager and define multiple persistence units. It is possible to use second-level caching, audit (with Hibernate Envers support), and metrics. It has some limitations like no support of JPA callbacks. Additionally, we can use multi-tenancy based on different schemas or different databases.

Reactive

  • We can use data sources for DB2, MySQL/MariaDB, and Postgres with clients for reactive database querying base on Vert.x add-ons.
  • They use the Mutiny library and support transactions. We can define multiple data sources. As for blocking clients, the framework provides a health check and metrics for reactive connection too.

Quarkus provides the Panache extension, enabling the active record pattern or the repository approach in our project. The former makes it possible to write more concise code than with JPA — we call all database operations using entities instead of Session or EntityManager APIs.

The latter is similar to the approach presented by Micronaut Data or Spring Data. Here, we are using a base interface defining some fundamental operations like finding or persisting an entity. We can extend those with custom methods defined in our interfaces (repositories), extending the base one. The entity model remains anemic.

Both approaches work with PanacheQuery instances, offering advanced querying, including paging, sorting, and projection of results, plus usage of named and custom HQL queries.

For blocking (with Agroal) and reactive extensions, the framework automatically adds health checks for data sources’ readiness if the SmallRye Health extension is on the classpath.

For the Agroal extension, we can enable the automatic collection of metrics based on Micrometer or SmallRye Metrics integrations.

Quarkus offers an interesting extension based on JPA, which is Blaze-Persistence. It is developed by a third-party but is part of the Quarkus Platform. The extension provides a fluent query builder API and entity views that fetch only-required data from queries instead of whole entities.

In terms of integration with NoSQL resources, Quarkus provides the following extensions:

  • MongoDB with the possibility to define multiple data sources and support of blocking and reactive clients (as in Micronaut);
  • Redis connection in preview mode, based on Vert.x Redis Client;
  • Neo4j offering blocking, async, and reactive connection modes; however, it is in the preview mode;
  • Cassandra based on DataStax driver providing blocking and reactive connectivity.

We can have metrics and connection health checks automatically enabled for all NoSQL extensions above if SmallRye Health and SmallRye Metrics are on the classpath.

As for the Hibernate ORM extension, MongoDB has the extra add-on, integrating access to the database with Panache. Here, we can choose between the active record and the repository patterns as well. However, although MongoDB supports ACID transactions, the extension doesn’t provide this feature.

jOOQ has no official support available. However, we can base on examples of projects from GitHub.

While the framework provides the Narayana Transactions extension implementing the JTA standard, we can use Software Transactional Memory in Quarkus applications too.

Quarkus provides extensions responsible for SQL database schema management with Liquibase and Flyway too. We can enable them for multiple data sources as well.

Messaging implementation

Quarkus offers the following integrations with messaging systems:

  • Kafka: Uses MicroProfile Reactive Messaging and its implementation SmallRye Reactive Messaging. Message producers and consumers are defined declaratively, using annotations put on methods. One may enable a health check endpoint with SmallRye Health on the classpath. The framework supports data serialization to JSON format out of the box with Jackson or JSON-B components correctly configured (meaning we have de-/serializers of our domain types registered). By default, the processing of messages is non-blocking, done on an event loop thread; we can switch to blocking processing, running on the worker threads pool. It is possible to specify a back pressure policy when writing a producer. If we created a producer using annotations, we could use Mutiny API to provide expected behavior on message overflow. When using the imperative style with an Emitter instance, the back-pressure strategy can be defined using annotations. We can manually commit the offset of a given message or leave it to the extension declaring whether the acknowledgment should occur before or after processing. When consuming messages, we can accept the whole `Message` instance (payload with all metadata) or work only on the payload part. Unfortunately, the documentation doesn’t mention batch processing for the consumer side and I haven’t found in the connector code support for this handy feature. The extension enables Kafka Streams, allowing to create streams’ topologies using registered producers and provides the `KafkaStreams` instance as a registered bean.
  • AMQP: As for Kafka integration, this one bases on SmallRye Reactive Messaging and uses the same set of annotations as well, so we define producers and consumers in a declarative way. However, we still can imperatively send messages.
  • MQTT: In the same way, we can use the extension for MQTT. It uses MicroProfile Messaging with SmallRye implementation and bases on the same annotations to provide producer and consumer instances.
  • JMS integration is implemented by two clients: Artemis JMS and Apache Qpid JMS. When defining and using message producer or consumer, details are the same since both extensions utilize JMS API, differing only with configuration details. Producers and consumers may be created as runnable processes.

An interesting feature of the MicroProfile Reactive Messaging spec is using in-memory streams based on @Incoming and @Outgoing annotations - the same used to define producers and consumers for the integrations listed above.

In terms of messaging, the Quarkus framework has yet another feature. It is an event bus allowing registered beans to communicate asynchronously based on addresses. The bus comes from Vert.x toolkit and provides three delivery mechanisms:

  • point-to-point,
  • publish/subscribe,
  • and request/reply (limited to single events).

On the Micronaut side, we have four external integrations as well:

  • Kafka: The integration uses a declarative way to provide producers and consumers as well. With annotations available, we can configure the producer’s topic, message key, and headers values. Producer methods may return reactive types we can subscribe to. Additionally, we have an option to send messages in batches. Message consumers may work on ConsumerRecord instances providing payload and all metadata for a given message. Alternatively, we can specify individual values (like the message key, the payload, offset, and other) as the consumer method parameters. We can consume received messages in parallel (within a given group) and using batches. For offsets committing, Microunaut provides several types of automatic behavior and an option to process it manually. The integration uses a listener exception handler to deal with exceptions coming from processing incoming messages. The framework provides health check endpoint, metrics, and distributed tracing depending on additional integrations added to the classpath. As in Quarkus, Micronaut supports Kafka Streams as well. We can define stream topologies and use Interactive Query Service. Additionally, the framework creates a health endpoint for the streams as well.
  • MQTT: The extension for the MQTT protocol has two distributions, supporting MQTT v3 and v5. The integration uses the Eclipse Paho Client. Based on annotations, we can provide a topic, QoS, and retained settings for publishers. Additionally, publishers may use a range of reactive types. Subscribers have an option to trigger acknowledgment manually. The extension offers customized parameters binding in messages and custom SerDes. Moreover, we can use SSL connections to MQTT brokers.
  • Nats.io: Micronaut integrates with the NATS messaging system as well. It uses the external Java client for communication, while the producers’ and consumers’ definition is provided in a similar way as for Kafka and MQTT (using annotations). There is support for queue groups and the RPC calls mechanism in subscribers. The framework provides a health endpoint if there is the Micronaut Management module on the classpath.
  • RabbitMQ: The same declarative philosophy can be found in the RabbitMQ integration. We have dedicated annotations for defining producers and consumers. The component supports the definition of multiple connections to create consumers executed on different thread pools. It uses listener exception handler to deal with exceptions coming from processing incoming messages. RabbitMQ offers an RPC mechanism using direct reply-to, and the extension supports it as well. The extension adds a health endpoint and metrics collection automatically if there are required components on the classpath.

Summary

While both frameworks aren’t in the market as long as the Spring Boot, they provide many features and useful integrations. Which framework is better? There is no straight answer to this. From a bird’s eye view, both suit the most recent applications’ requirements well. The decision on which one to use in your project depends on details.

Is there an integration with the system you’re going to use? How would you like to connect to an SQL database? Or does the messaging integration provide a feature you would need? These are the types of questions you would need to answer before making the decision.

That’s the first part. In the next one, I’m going to look at web and security support with cloud features.

If there is anything in this post you would like to have described with more details and code examples, let me know.

--

--

Passionate software developer. Focused on a good design and the best quality.