At present, there is no lack of frameworks for creating miсroservices in Java and Kotlin. In this article, we will be considering the following frameworks:
Based on the above, four services were created that can interact with each other through the HTTP API using the Service Discovery pattern implemented by
Consul. Thus, they form a heterogeneous (at the level of frameworks) microservice architecture (hereafter referred to as MSA):
In this article, the implementation of microservices on each of the frameworks is briefly considered (for more details, check monorepo’s source code on
GitHub and the parameters of the obtained applications are compared.
Let’s define a set of requirements for each service:
Creating an Application From Scratch
To generate a new project on one of the frameworks, you can use web starter or other options (for instance, build tool, or IDE) considered in a guide:
Groovy, Java, Kotlin
Java, Kotlin, Scala
Groovy, Java, Kotlin
The framework was created in Oracle for internal use, subsequently becoming open-source. There are two development models based on this framework: Standard Edition (SE) and MicroProfile (MP). In both cases, the service will be a regular Java SE program. Learn more about the differences on
In short, Helidon MP is one of the implementations of Eclipse
MicroProfile, which makes it possible to use many APIs, both previously known to Java EE developers (JAX-RS, CDI, for example) and newer ones (Health Check, Metrics, Fault Tolerance, etc.). In the Helidon SE model, the developers were guided by the principle of “No magic,” which is expressed, in particular, in the smaller number or complete absence of annotations necessary to create the application.
Helidon SE was selected for the development of microservice. Among other things, it lacks the means for Dependency Injection, so for this purpose,
Koin was used. The following is the class containing the main method. To implement Dependency Injection, the class is inherited from
First, Koin starts, then the required dependencies are initialized and
startServer() method is called, where an object of the
WebServer type is created to which the application configuration and routing settings are passed; after starting the application is registered in Consul:
Routing is configured as follows:
The application uses the config in
It is also possible to use files in the formats JSON, YAML, and properties for configuration (in more detail on
Helidon config docs).
The framework is written in and designed for Kotlin. As in Helidon SE, Ktor hasn’t DI out of the box, so before starting the server, dependencies should be injected using Koin:
The modules needed by the application are specified in the configuration file (only the HOCON format is possible; more details on configuring the Ktor server on
Ktor config docs), content of which is presented below:
In Ktor and Koin, the term “module” is used with different meanings. In Koin, a module is an analog of the application context in the Spring Framework. The Ktor module is a user-defined function that accepts an object of type
Application and can configure the pipeline, install
features, register routes, process requests, etc.:
This code snippet configures the routing of requests, in particular, the static resource
Ktor-service may contain features. A feature is a functionality that is embedded in the request-response pipeline (
Compression, and others in the example code above). You can implement own features, for example, below is the code implementing the Service Discovery pattern in conjunction with client-side load balancing based on the round-robin algorithm:
The main logic is in
install method: in a time of
Render request phase (which is performed before
Send phase), firstly a name of a called service is determined, then at
consulClient list of instances of the service is requested, then an instance defined by Round-robin algorithm is calling. Thus, the following call becomes possible:
Micronaut is developed by the creators of the
Grails framework and inspired by the experience of building services using Spring, Spring Boot, and Grails. The framework supports Java, Kotlin, and Groovy languages; maybe, it will be Scala support. Dependencies are injected at compile-time, which results in less memory consumption and faster application launch compared to Spring Boot.
The main class looks like this:
Some components of an application based on Micronaut are similar to their counterparts in the Spring Boot application. For example, below is the controller code:
Support for Kotlin in Micronaut is built upon the
kapt compiler plugin (more details on
Micronaut Kotlin guide). The build script is configured as follows:
The following is the contents of the configuration file:
JSON, properties, and Groovy file formats can also be used for configuration (for more detail, check out this
Micronaut config guide).
The framework is introduced as a tool appropriate to present challenges such as new deployment environments and application architectures; applications written on the framework will have low memory consumption and boot time. Also, there are benefits for a developer, for instance, live reload out of the box.
There is no main method in the source code of your Quarkus application. For more info, review this
issue on GitHub).
The Controller looks very typical for those who are familiar with Spring and/or Java EE:
As you can see, beans are injected by
@Inject annotation, and for each injected bean, you might specify a scope, for instance:
Creating of REST clients to other services is as simple as creating an interface using the proper JAX-RS and MicroProfile annotations:
As you can see, for the
baseUri, parameter services' names are used. But now, there is no built-in support of Service Discovery (
Eureka), or it doesn’t work properly (
Consul) because the framework mainly targets cloud environments. Thus, as in Helidon and Ktor services,
Consul Client for Java library is used. At first, it is needed to register the application:
Then, it is needed to resolve services' names to its particular location; the resolution is implemented simply by replacement URI of
requestContext with the location of a service obtained from Consul client:
Quarkus supports configuration via properties or YAML files (more details on
Quarkus config guide).
Spring Boot Service
The framework was created to simplify the development of applications using the Spring Framework ecosystem. This is achieved through auto-configuration mechanisms for used libraries.
The following is the controller code:
The microservice is configured by a YAML file:
It is also possible to use properties files for configuration (more on
Spring Boot config docs).
Before launching microservices, you need to
install Consul and start the agent — for example, like this:
consul agent -dev.
You can start microservices from:
Users of IntelliJ IDEA may see something like the following:
To launch Quarkus service, you need to start
quarkusDev Gradle task.
Execute in the project’s root folder:
java -jar helidon-service/build/libs/helidon-service-all.jar
java -jar ktor-service/build/libs/ktor-service-all.jar
java -jar micronaut-service/build/libs/micronaut-service-all.jar
java -jar quarkus-service/build/quarkus-service-1.0.0-runner.jar
java -jar spring-boot-service/build/libs/spring-boot-service.jar
After starting of all microservices on
, you will see:
As an example, the results of testing the API of Helidon service:
You can test API of any microservice using
Postman ( collection of requests), IntelliJ IDEA HTTP client ( collection of requests), browser, or other tools. In the case of using the first two clients, you need to specify the port of the called microservice in the corresponding variable (in Postman, it is in the menu of the collection → Edit → Variables, and in the HTTP Client — in the environment variable specified in this file). When testing the second method of the API, you also need to specify the name of the microservice requested “under the hood.” The answers will be similar to those given above.
Comparison of Applications' Parameters
After releases of the new versions of the frameworks, the following results (which are, of course, unscientific and can be reproduced only on my machine) will inevitably become outdated; you can check updated results by yourself using
this GitHub project and the frameworks' new versions (which are specified in ).
To preserve the simplicity of setting up applications, no transitive dependencies were excluded from the build scripts, therefore, the size of the Spring Boot service uber-JAR significantly exceeds the size of analogs on other frameworks (since using starters not only necessary dependencies are imported; if desired, the size can be significantly reduced):
Artifact size, MB
Spring Boot service
The start time of each application is inconstant and falls into some “window”; the table below shows the start time of the artifact without specifying any additional parameters:
Start time, seconds
Spring Boot service
It is worth noting that if you “clean” the application on Spring Boot from unnecessary dependencies and pay attention to setting up the launch of the application (for example, scan only the necessary packages and use lazy initialization of beans), then you can significantly reduce the launch time.
For each microservice, the following was determined:
A minimum amount of heap memory (determined by using
-Xmx parameter) required to run a healthy (responding to different types of requests) microservice
A minimum heap memory required to pass a load test 50 users * 1000 requests
A minimum heap memory required to pass a load test 500 users * 1000 requests
Heap memory is only a part of the total memory allocated for an application. If you want to measure overall memory usage, you can use, for example,
For load testing,
Gatling and Scala script were used. The load generator and the service being tested were run on the same machine (Windows 10, 3.2 GHz quad-core processor, 24 GB RAM, SSD). The port of the service is specified in the Scala script. Passing a load test means that microservice has responded to all requests for any time.
A Minimum amount of heap-memory, MB
For start a
For a load of 50 * 1000
For a load of 500 * 1000
Spring Boot service
It should be noted that all microservices use Netty HTTP server.
The required functionality — a simple service with the HTTP API and the ability to function in MSA — was succeeded in all the frameworks considered. It’s time to take stock and consider their pros and cons.
Microservice has not been implemented on this framework, so I will note only one point.
Eclipse MicroProfile implementation
Essentially, MicroProfile is Java EE optimized for MSA. Thus, firstly, you have access to the whole variety of Java EE API, including that developed specifically for MSA. Secondly, you can change the implementation of MicroProfile to any other (Open Liberty, WildFly Swarm, etc.)
“Sharpened” under Kotlin, that is, development in other languages can be impossible or is not worthwhile
Microframework — See Helidon SE
On the one hand, the framework is not included in the two most popular Java development models (Spring-like (Spring Boot/Micronaut) and Java EE/MicroProfile), which can lead to:
On the other hand, dissimilarity to “classic” Spring and Java EE lets look at the process of development from a different angle, perhaps more consciously
AOT — As previously noted, the AOT can reduce the start time and memory consumption of the application as compared to the analog on Spring Boot
Spring-like development model — programmers with experience with Spring framework won’t take much time to master this framework
Application parameters — Good results for all parameters
Platform maturity and ecosystem — It's a framework for every day. For most everyday tasks, there is already a solution in the programming paradigm of Spring, that is, in the way that many programmers are used to. Also, development is simplified by the concept of starters and auto-configuration
A large number of specialists in the labor market, as well as a significant knowledge base (including documentation and answers on Stack Overflow)
Perspective — I think many will agree that Spring will remain the leading Java/Kotlin framework in the near future
Parameters of application — Application in this framework wasn’t among the leaders, however, some parameters, as noted earlier, you can optimize yourself. It is also worth remembering about the presence of the project
Spring Fu, which is in active development and the use of which reduces these parameters
Also, we can highlight common problems that new frameworks have, but Spring Boot does not:
The considered frameworks belong to different weight divisions: Helidon SE and Ktor are
microframeworks; Spring Boot and Micronaut are full-stack frameworks; Quarkus and Helidon MP are MicroProfile frameworks. A microframework’s functionality is limited, which can slow down the development. To clarify the possibility of implementing a particular functionality based on a particular framework, I recommend that you familiarize yourself with its documentation.
I don’t dare to judge whether this or that framework will “shoot” in the near future, so in my opinion, for now, it’s best to continue to observe developments using the existing framework for solving work tasks.
At the same time, as was shown in the article, new frameworks win Spring Boot on the considered parameters of the applications. If any of these parameters are critically important for one of your microservices, then perhaps it’s worth to pay attention to the frameworks that showed the best results. However, we should not forget that Spring Boot, firstly, continues to improve, and secondly, it has a huge ecosystem and a significant number of Java programmers familiar with it. Lastly, there are other frameworks not covered in this article: Vert.x, Javalin, etc.
Thank you for reading!
P.S. Thanks to
artglorin for helping with this article Further Reading
All About Spring Boot [Tutorals and Articles]
Thoughts on Quarkus
A Quick Guide to Microservices With the Micronaut Framework