Blog
navigate_next
Java
SpringBoot vs Quarkus vs Micronaut
Pratik Dwivedi
February 5, 2024

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.

Introduction

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

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:

  • SpringBoot allows developers to create standalone applications with little or no configuration. It is designed specifically for creating dynamic web pages and microservices.
  • SpringBoot allows developers to focus on business logic and securing their applications, rather than waste time in the framework plumbing.

Cons:

  • As SpringBoot is built over the Spring framework, the whole framework must load and occupy system resources to run, in addition to your application.
  • Since the framework itself occupies significant JVM memory and uses CPU cycles, it's not the ideal option if you're using only a subset of SpringBoot's inbuilt functionalities.
  • SpringBoot generates a large number of preconfigured dependencies, which results in a large deployment file but most of them might remain unused in your application.

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.

Quarkus:

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:

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.  

CODE SAMPLES:

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


@SpringBootApplication
public class SpringBootMathResource {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootMathResource.class, args);
    }
}

@RestController
@RequestMapping("/math")
class SpringBootMathController {

    @GetMapping("/factorial/{number}")
    public long factorial(@PathVariable int number) {
        if (number < 0) {
            throw new IllegalArgumentException("Input should be a non-negative integer");
        }
        if (number == 0 || number == 1) {
            return 1;
        }
        long result = 1;
        for (int i = 2; i <= number; i++) {
            result *= i;
        }
        return result;
    }

    @GetMapping("/cube/{number}")
    public long cube(@PathVariable int number) {
        return (long) Math.pow(number, 3);
    }

    @GetMapping("/square/{number}")
    public long square(@PathVariable int number) {
        return (long) Math.pow(number, 2);
    }
}

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


@Path("/math")
public class QuarkusMathResource {

    @GET
    @Path("/factorial/{number}")
    @Produces(MediaType.TEXT_PLAIN)
    public long factorial(@PathParam("number") int number) {
        if (number < 0) {
            throw new BadRequestException("Input should be a non-negative integer");
        }

        long result = 1;
        for (int i = 2; i <= number; i++) {
            result *= i;
        }
        return result;
    }

    @GET
    @Path("/cube/{number}")
    @Produces(MediaType.TEXT_PLAIN)
    public long cube(@PathParam("number") int number) {
        return (long) Math.pow(number, 3);
    }

    @GET
    @Path("/square/{number}")
    @Produces(MediaType.TEXT_PLAIN)
    public long square(@PathParam("number") int number) {
        return (long) Math.pow(number, 2);
    }
}

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


@Controller("/math")
public class MicronautMathResource {

    @Get("/factorial/{number}")
    public long factorial(@PathVariable int number) {
        if (number < 0) {
            throw new IllegalArgumentException("Input must be a non-negative integer");
        }
        if (number == 0 || number == 1) {
            return 1;
        } else {
            return number * factorial(number - 1);
        }
    }

    @Get("/cube/{number}")
    public int cube(@PathVariable int number) {
        return number * number * number;
    }

    @Get("/square/{number}")
    public int square(@PathVariable int number) {
        return number * number;
    }
}

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.

Comparative Summary of SpringBoot  VS  Quarkus  VS  Micronaut for their code samples:

  1. The basic operations are performed similarly in all three frameworks, i.e. inside the methods almost similar Java code is used.
  2. You can replace all of these simple classes with classes that use the abstractions of Entity/Repository/Service/ classes.
  3. All use annotations to mark the methods and specify a method's operation( GET/POST/PUT..) and the URL path that will trigger the method.These annotations are different in some places.

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.

Scenarios where SpringBoot is a better choice

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.

  • So, SpringBoot is well suited not only for monolithic applications but microservices as well.
  • If the software ecosystem in your organization is using a lot of Spring and SpringBoot and has a lot of expertise in that technology, it's better to stick to it; as SpringBoot is also in active and progressive development. Newer SpringBoot versions lend themselves much better( than older versions) for containerization/serverless/microservices development etc.
  • Spring Boot has the advantage, especially If you need Service Discovery servers (like those in Spring Cloud), and your application will primarily run outside Kubernetes clusters.
  • If only a subset of your application's functionality is being exposed via APIs/microservices OR if your application is not entirely focused on microservices, SpringBoot is a good choice.
  • The latest versions of SpringBoot provide appreciable support for running inside containers and serverless infrastructure, so if you plan to move to the cloud in the future, SpringBoot is not a bad choice.
  • Also, SpringBoot has the widest support for legacy applications and popular frameworks.SpringBoot has the largest number of features and plugins to offer and is the most powerful and versatile among these three.

Next, we discuss scenarios where your application( or a large part of it) is specifically developed for microservices or for running inside cloud containers.

Scenarios where Quarkus is a better choice

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.

  • So, if you're developing serverless applications and/or apps to run on the above cloud environments Quarkus is probably the best choice.
  • Using Quarkus you can develop applications that can scale to fluctuating and demanding workloads, while still maintaining appreciable throughput.
  • Quarkus is the choice if you will run your workloads on Kubernetes only and serverless will form the backbone of your application. Quarkus plugins generate an AWS Cloud Formation skeleton and have many integration points with AWS Serverless Application Model(SAM), and a proxy to simulate lambda calls locally.
  • Quarkus is based on the Java EE specs and uses a lot of Vert.x underneath it. So, if you’re comfortable with these two technologies or your application needs them, Quarkus is the choice.
  • Quarkus uses Panache for Hibernate-based data persistence layer, it simplifies Hibernate ORM mappings and its hibernate implementation is a bit more modern.Quarkus supports Reactive SQL clients for IBM Db2, PostgreSQL, MariaDB/MySQL, Microsoft SQL Server, and Oracle. This can be another indicator of whether Quarkus fits your requirements.

Scenarios where Micronaut is a better choice

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.

  • If you have an existing application and now want to expose some of its features via APIs or microservices, Micronaut is a good choice.
  • Micronaut has good support from Reactive programming and supports multiple service discovery tools such as Eureka and Consul. So if you’re using any of these, Micronaut is a good choice.
  • Micronaut has the most sophisticated and efficient Querying model among these three. Micronaut querying mode is based on GORM, a powerful Groovy-based data access toolkit for the JVM. It has a fast powerful set of APIs for querying not only relational but also NoSQL(non-relational) data. So, if your application needs fast and efficient querying over your data layer, Micronaut is the choice.
  • Micronaut is also a good choice over traditional frameworks if you're developing a new application that must be fast and scalable, and also needs to have good throughput for microservices/APIs.
  • You can also consider Micronaut to add a microservices layer over your existing SpringBoot-based monolith. Not to forget that Micronaut is GraalVM ready and runs very well on cloud containers supporting serverless applications. So, if you choose to use Micronaut you will not miss any other framework.

General Guidelines and Tips on Selecting Frameworks

  • Consider how much a framework is in active development and how vibrant or interactive its community is. Frameworks under progressive development, with a cooperative community, are good choices.
  • Check whether its creation team is sensitive to issues raised on GitHub for example. If its fixes and new additions incorporate feedback and user requests, it's a good choice.
  • Your company vision on how the product will evolve in the future OR how the services rendered, can also give decisive clues on which framework to use.

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!

Pratik Dwivedi
February 5, 2024
Use Unlogged to
mock instantly
record and replay methods
mock instantly
Install Plugin