In this post, we will compare three of the most popular frameworks for developing Java applications, specifically to develop microservices or REST APIs.
SpringBoot is the oldest and most popular among the three, whereas Quarkus and Micronaut are comparatively newer frameworks that were developed for specific purposes, keeping in mind the demands on modern applications, and the advancements in the cloud infrastructure they run on.
Spring is a revolutionary and very useful framework that eases Java development and adds security and clean architecture to Java development.
Spring uses a reflection-based Inversion of Control mechanism, so it has to load the whole framework and cache the reflection data for every bean in the application context, and then the application classes are loaded to run over the framework. This makes SpringBoot-based applications slower to load and heavier to run, as compared to some other modern frameworks. While this is good for monolithic applications that implement multiple features/functionalities, it might not be a good choice if you want to build lightweight and fast microservices.
For a small codebase that implements a small set of features or is focused towards a single goal, like providing API-based access to your business services, this might be an overkill.
Hence the topic of our discussion.
First, we discuss SpringBoot and its strengths and weaknesses.
SpringBoot, no doubt, is an excellent framework.
SpringBoot is built over the Spring framework, which itself is a collection of its sub-frameworks like Spring AOP, Spring ORM, Spring Web MVC, etc. So, to make good use of it one has to specify many dependencies and configurations, mandated by its sub-frameworks.
So, SpringBoot was specifically built to ease the development of microservices/REST APIs and make a production-ready application in less time.
SpringBoot has many features auto-configured and one just needs to fine-tune an existing configuration to achieve a desired base functionality.
E.g. SpringBoot provides embedded servers(Tomcat and Jetty), an in-memory database, auto-configuration of dependencies, etc.Then the application's business logic can be implemented on the pre-configured framework/rails of SpringBoot.
With all the improvements and latest features introduced in Spring Framework 6 and SpringBoot 3, it has kept pace with the changing times and demands of modern applications.
Pros:
Cons:
SpringBoot is a good option if your application uses most of the features of Spring as well as the SpringBoot platform. If you're building lightweight microservices, and/or if your primary application (that exposes its features via microservices/REST APIs) is not a monolith having multiple functional modules, you should consider using other lightweight frameworks.
Two of which, Quarkus and Micronaut, we discuss below.
Traditional Java architecture as well as SpringBoot were designed to run monolithic applications very well. With the advancement in cloud-based virtualization techniques, there was a need for a Java runtime that's not only lightweight and free of frills and embellishments but also fast and resource-efficient.
Hence Quarkus was designed to run on container orchestration platforms such as Kubernetes, using the best-of-the-breed Java libraries and standards. Its container-first philosophy ensures that at any instance of time, only that code is loaded into the JVM which has a clear execution path at runtime.
Also, unlike SpringBoot, Quarkus uses static class binding instead of reflection, so that it does not have to load all possible class associations during startup.This further reduces the memory footprint and makes the JVM nimble, quick to scale up, and CPU/Memory efficient.
Moreover, Quarkus is designed to run on the GraalVM, which offers ahead-of-time (AOT) compilation of Java programs and compiles Java code into operating-system and architecture-specific native code, making it blazing fast as compared to traditional frameworks. We wrote an in-depth post about graalVM in context of java 8 to java 21 migration.
Quarkus readily adapts to current industry standards ( like micro profile, JPA, JAX-RS, etc), and with Quarkus security is just a configuration instead of writing and wiring code together.
Another distinctive feature of Quarkus is its Dev Services feature. Imagine you’re using many extensions and forget/don’t know how to configure some of them.Quarkus will detect unconfigured extensions and will automatically start the relevant service( using Test Containers) and provide the necessary wiring to help your application use these extensions. Some of the services supported by this feature are KeyCloak, Kafka, RabbitMQ, AMQP, MongoDB, Neo4J, and even Elastic Search, etc.
Micronaut was developed specifically for building microservices and serverless applications that are modular and easily testable. Micronaut focuses on using minimal reflection, efficient resource utilization, and compile-time dependency injection.
Instead of using reflection, Micronaut integrates directly with the Java compiler through annotation processors and computes an additional set of classes that perform dependency injection later.
Unlike reflection-based IoC frameworks like SpringBoot, Micronaut does not load reflection data for every bean in the application context, resulting in quick startup speed and low memory consumption, no matter how heavy the application codebase is. This further leads to faster response time and much higher throughput.
Moreover, Micronaut Data allows you to simplify and speed up your database interactions by simply creating interfaces with the correct names. This not only saves a huge amount of time but also adds clarity and easy discernibility to your code.
Micronaut is specifically designed to ease the development of microservices with support for many different messaging systems like Kafka/RabbitMQ/JMS/MQTT/NATS.io. It's GraalVM ready and supports serverless applications, that run very well on cloud-based containers on almost all popular cloud providers.
Micronaut is a polyglot framework that supports not only Java but languages like Groovy and Kotlin, with Scala on the roadmap.
Let's now dig a bit deeper and write some code to get familiar with these frameworks and to know their subtle differences.
For the sake of simplicity and to ease comparison, we will write a simple class each, using the above three frameworks.
We will try to use some annotations so that you can easily compare and know the subtle differences.
SpringBoot
We specify that this class will be acting as a rest controller via the <span class="pink">@RestController</span>
annotation, and any request that has the URL of the form "/math" should be directed to this class, via the <span class="pink">@RequestMapping("/math")</span>
annotation.
SpringBoot allows you to use <span class="pink">@GetMapping(”URL part”)</span>
or <span class="pink">@PostMapping</span>
, <span class="pink">@PutMapping</span>
, etc to specify that this method will service the GET/POST/PUT requests. <span class="pink">@PathVriable</span>
allows mapping the variable in the URL to the parameter for the method. Rest is simple Java code, you can appreciate how SpringBoot annotations allow you to just tag/specify certain properties, and SpringBoot does all the mapping and control redirection, to ease your job.
Quarkus
Quarkus is slightly different syntactically, e.g. it provides <span class="pink">@Consumes</span>
and <span class="pink">@Produces</span>
annotations to specify the data type expected as input parameters and the return types respectively. <span class="pink">@Produces("application/json")</span>
&
<span class="pink">@Consumes("application/json")</span>
You can use <span class="pink">@RunOnVirtualThread</span>
to invoke the annotated method on a virtual thread instead of a regular platform thread.OR <span class="pink">@Transactional</span>
annotation to define your transaction boundaries, e.g. on your entry method
Instead of a <span class="pink">@PathParam</span>
you can pass an object parameter to the method, e.g. "cube(int number)
" OR "cube(Integer myInt)
"
Micronaut
To pass a different object parameter to the method you could use <span class="pink">@Body</span>
annotation.It allows to specify the parameter the body of the request should bind to, e.g. "<span class="pink">save(@Body Person person)</span>
" OR "<span class="pink">alert (@Body String alertmessage)</span>
"
Read more about the body annotation here.
Let's now talk about the scenarios where each of these frameworks could be used. The thoughts below are based on our knowledge and experience, without any bias or prejudice.
SpringBoot (as well as Spring) was designed specifically to code monolithic applications quickly by avoiding a lot of boilerplate code for Database access, IoC, class associations, and control flow.SpringBoot lets you focus on your business logic instead of working on configuration and framework implementation.SpringBoot combines all the necessities for microservices development, such as an application context and an auto-configured, embedded web server.
Next, we discuss scenarios where your application( or a large part of it) is specifically developed for microservices or for running inside cloud containers.
Quarkus is specifically designed to work inside Kubernetes cloud environments and serverless applications running on AWS Lambda, Google Cloud Functions, Knative, and Azure Functions. Quarkus provides native image support in these environments, as well as an automated process for creating Kubernetes resources.Apart from Java, Quarkus supports Scala, Groovy, and Kotlin via extensions.Also, Quarkus does some build-time configuration enhancements rather than doing them at runtime and applies classpath trimming to include only essential classes and dependencies in the final executable.
Applications developed using Quarkus will have very fast startup times and will scale seamlessly over the underlying cloud containers when needed. Quarkus application will consume much less CPU and memory resources while running.Lastly, Quarkus is newer and leaner but has limited community support compared to SpringBoot.
Micronaut is specifically designed to ease the development of microservices with support for many different messaging systems like Kafka/RabbitMQ/JMS/MQTT/NATS.io.
Micronaut has good support for many SQL databases with its efficient connection pooling and AOP(ahead-of-time) compilation to quickly process defined queries.Lastly, Micronaut is newer and leaner but has limited community support compared to SpringBoot.
We have discussed the rationale behind the emergence of SpringBoot, Quarkus, and Micronaut frameworks, and how they differ from each other. We have written some code to highlight their differences and scenarios where they might be a good fit.
Thanks for reading!