DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Spring Boot - Microservice- JaxRS Jersey - HATEOAS - JerseyTest - Integration
  • Building Mancala Game in Microservices Using Spring Boot (Part 3: Web Client Microservice Implementation Using Vaadin)
  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • Component Tests for Spring Cloud Microservices

Trending

  • How To Introduce a New API Quickly Using Quarkus and ChatGPT
  • Code Reviews: Building an AI-Powered GitHub Integration
  • Apple and Anthropic Partner on AI-Powered Vibe-Coding Tool – Public Release TBD
  • The End of “Good Enough Agile”
  1. DZone
  2. Coding
  3. Frameworks
  4. Review of Microservices Frameworks: A Look at Spring Boot Alternatives

Review of Microservices Frameworks: A Look at Spring Boot Alternatives

Learn more about these Spring Boot alternatives for building Java and Kotlin microservices.

By 
Roman Kudryashov user avatar
Roman Kudryashov
DZone Core CORE ·
Updated Jul. 19, 21 · Review
Likes (60)
Comment
Save
Tweet
Share
86.3K Views

Join the DZone community and get the full member experience.

Join For Free


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... but for a deeper refresher on building a microservice API with Spring Boot click here.

Name Version Year of a first release Developer GitHub

Helidon SE

1.4.1

2019

Oracle

link

Ktor

1.3.0

2018

JetBrains

link

Micronaut

1.2.9

2018

Object Computing

link

Quarkus

1.2.0

2019

Red Hat

link

Spring Boot

2.2.4

2014

Pivotal

link

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):

target architecture

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:


  • Technology stack:

    • JDK 13

    • Kotlin

    • Gradle (Kotlin DSL)

    • JUnit 5

  • Functionality (HTTP API):

    • GET /application-info{?request-to=some-service-name}

      Returns some basic information about microservice (name, framework, year of release of the framework); when you specify in the request-to parameter name of another microservice, then an analogous request is executed to its HTTP API

    • GET /application-info/logo

      Returns an image

  • Implementation:

    • Configuration using a text file

    • Use of Dependency Injection

    • Tests that check the HTTP API is working properly

  • MSA:

    • Use of Service Discovery pattern (registration in Consul, request to the HTTP API of another microservice by its name with the client-side load balancing)

    • Building an uber-JAR artifact

Prerequisites


  • JDK 13

  • Consul

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:

Name Web starter Guide Supported languages

Helidon

link (MP)

link (SE)

link (MP)

Java, Kotlin

Ktor

link

link

Kotlin

Micronaut

- (CLI)

link

Groovy, Java, Kotlin

Quarkus

link

link

Java, Kotlin, Scala

Spring Boot

link

link

Groovy, Java, Kotlin

Helidon Service

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 this page.

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 KoinComponent.

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:

Kotlin
 




x
40


 
1
object HelidonServiceApplication : KoinComponent {
2

            
3
    @JvmStatic
4
    fun main(args: Array<String>) {
5
        val startTime = System.currentTimeMillis()
6
        startKoin {
7
            modules(koinModule)
8
        }
9

            
10
        val applicationInfoService: ApplicationInfoService by inject()
11
        val consulClient: Consul by inject()
12
        val applicationInfoProperties: ApplicationInfoProperties by inject()
13
        val serviceName = applicationInfoProperties.name
14

            
15
        startServer(applicationInfoService, consulClient, serviceName, startTime)
16
    }
17
}
18

            
19
fun startServer(
20
    applicationInfoService: ApplicationInfoService,
21
    consulClient: Consul,
22
    serviceName: String,
23
    startTime: Long
24
): WebServer {
25
    val serverConfig = ServerConfiguration.create(Config.create().get("webserver"))
26

            
27
    val server: WebServer = WebServer
28
        .builder(createRouting(applicationInfoService))
29
        .config(serverConfig)
30
        .build()
31

            
32
    server.start().thenAccept { ws ->
33
        val durationInMillis = System.currentTimeMillis() - startTime
34
        log.info("Startup completed in $durationInMillis ms. Service running at: http://localhost:" + ws.port())
35
        // register in Consul
36
        consulClient.agentClient().register(createConsulRegistration(serviceName, ws.port()))
37
    }
38

            
39
    return server
40
}



Routing is configured as follows:
Kotlin
 




xxxxxxxxxx
1
22


 
1
private fun createRouting(applicationInfoService: ApplicationInfoService) = Routing.builder()
2
    .register(JacksonSupport.create())
3
    .get("/application-info", Handler { req, res ->
4
        val requestTo: String? = req.queryParams()
5
            .first("request-to")
6
            .orElse(null)
7

           
8
        res
9
            .status(Http.ResponseStatus.create(200))
10
            .send(applicationInfoService.get(requestTo))
11
    })
12
    .get("/application-info/logo", Handler { req, res ->
13
        res.headers().contentType(MediaType.create("image", "png"))
14
        res
15
            .status(Http.ResponseStatus.create(200))
16
            .send(applicationInfoService.getLogo())
17
    })
18
    .error(Exception::class.java) { req, res, ex ->
19
        log.error("Exception:", ex)
20
        res.status(Http.Status.INTERNAL_SERVER_ERROR_500).send()
21
    }
22
    .build()



The application uses the config in HOCON format:
JSON
 




xxxxxxxxxx
1
11


 
1
webserver {
2
  port: 8081
3
}
4

           
5
application-info {
6
  name: "helidon-service"
7
  framework {
8
    name: "Helidon SE"
9
    release-year: 2019
10
  }
11
}



It is also possible to use files in the formats JSON, YAML, and properties for configuration (in more detail on Helidon config docs).

Ktor Service

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:

Kotlin
 




xxxxxxxxxx
1
14


 
1
val koinModule = module {
2
    single { ApplicationInfoService(get(), get()) }
3
    single { ApplicationInfoProperties() }
4
    single { ServiceClient(get()) }
5
    single { Consul.builder().withUrl("http://localhost:8500").build() }
6
}
7

            
8
fun main(args: Array<String>) {
9
    startKoin {
10
        modules(koinModule)
11
    }
12
    val server = embeddedServer(Netty, commandLineEnvironment(args))
13
    server.start(wait = true)
14
}



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:
JSON
 




xxxxxxxxxx
1
21


 
1
ktor {
2
  deployment {
3
    host = localhost
4
    port = 8082
5
    environment = prod
6
    // for dev purpose
7
    autoreload = true
8
    watch = [io.heterogeneousmicroservices.ktorservice]
9
  }
10
  application {
11
    modules = [io.heterogeneousmicroservices.ktorservice.module.KtorServiceApplicationModuleKt.module]
12
  }
13
}
14

           
15
application-info {
16
  name: "ktor-service"
17
  framework {
18
    name: "Ktor"
19
    release-year: 2018
20
  }
21
}



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.:
Kotlin
 




xxxxxxxxxx
1
27


 
1
fun Application.module() {
2
    val applicationInfoService: ApplicationInfoService by inject()
3

           
4
    if (!isTest()) {
5
        val consulClient: Consul by inject()
6
        registerInConsul(applicationInfoService.get(null).name, consulClient)
7
    }
8

           
9
    install(DefaultHeaders)
10
    install(Compression)
11
    install(CallLogging)
12
    install(ContentNegotiation) {
13
        jackson {}
14
    }
15

           
16
    routing {
17
        route("application-info") {
18
            get {
19
                val requestTo: String? = call.parameters["request-to"]
20
                call.respond(applicationInfoService.get(requestTo))
21
            }
22
            static {
23
                resource("/logo", "logo.png")
24
            }
25
        }
26
    }
27
}



This code snippet configures the routing of requests, in particular, the static resource logo.png.

Ktor-service may contain features. A feature is a functionality that is embedded in the request-response pipeline (DefaultHeaders, 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:

Kotlin
 




xxxxxxxxxx
1
28


 
1
class ConsulFeature(private val consulClient: Consul) {
2

            
3
    class Config {
4
        lateinit var consulClient: Consul
5
    }
6

            
7
    companion object Feature : HttpClientFeature<Config, ConsulFeature> {
8
        var serviceInstanceIndex: Int = 0
9

            
10
        override val key = AttributeKey<ConsulFeature>("ConsulFeature")
11

            
12
        override fun prepare(block: Config.() -> Unit) = ConsulFeature(Config().apply(block).consulClient)
13

            
14
        override fun install(feature: ConsulFeature, scope: HttpClient) {
15
            scope.requestPipeline.intercept(HttpRequestPipeline.Render) {
16
                val serviceName = context.url.host
17
                val serviceInstances =
18
                    feature.consulClient.healthClient().getHealthyServiceInstances(serviceName).response
19
                val selectedInstance = serviceInstances[serviceInstanceIndex]
20
                context.url.apply {
21
                    host = selectedInstance.service.address
22
                    port = selectedInstance.service.port
23
                }
24
                serviceInstanceIndex = (serviceInstanceIndex + 1) % serviceInstances.size
25
            }
26
        }
27
    }
28
}



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:

Kotlin
 




xxxxxxxxxx
1


 
1
fun getApplicationInfo(serviceName: String): ApplicationInfo = runBlocking {
2
    httpClient.get<ApplicationInfo>("http://$serviceName/application-info")
3
}



Micronaut Service

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:

Kotlin
 




xxxxxxxxxx
1
10


 
1
object MicronautServiceApplication {
2

            
3
    @JvmStatic
4
    fun main(args: Array<String>) {
5
        Micronaut.build()
6
            .packages("io.heterogeneousmicroservices.micronautservice")
7
            .mainClass(MicronautServiceApplication.javaClass)
8
            .start()
9
    }
10
}



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:
Kotlin
 




xxxxxxxxxx
1
15


 
1
@Controller(
2
    value = "/application-info",
3
    consumes = [MediaType.APPLICATION_JSON],
4
    produces = [MediaType.APPLICATION_JSON]
5
)
6
class ApplicationInfoController(
7
    private val applicationInfoService: ApplicationInfoService
8
) {
9

           
10
    @Get
11
    fun get(requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo)
12

           
13
    @Get("/logo", produces = [MediaType.IMAGE_PNG])
14
    fun getLogo(): ByteArray = applicationInfoService.getLogo()
15
}



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:
Kotlin
 




xxxxxxxxxx
1
12


 
1
plugins {
2
    ...
3
    kotlin("kapt")
4
    ...
5
}
6

           
7
dependencies {
8
    kapt("io.micronaut:micronaut-inject-java:$micronautVersion")
9
    ...
10
    kaptTest("io.micronaut:micronaut-inject-java:$micronautVersion")
11
    ...
12
}



The following is the contents of the configuration file:
YAML
 




xxxxxxxxxx
1
16


 
1
micronaut:
2
  application:
3
    name: micronaut-service
4
  server:
5
    port: 8083
6

           
7
consul:
8
  client:
9
    registration:
10
      enabled: true
11

           
12
application-info:
13
  name: ${micronaut.application.name}
14
  framework:
15
    name: Micronaut
16
    release-year: 2018



JSON, properties, and Groovy file formats can also be used for configuration (for more detail, check out this  Micronaut config guide).

Quarkus Service

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:

Kotlin
 




xxxxxxxxxx
1
16


 
1
@Path("/application-info")
2
@Produces(MediaType.APPLICATION_JSON)
3
@Consumes(MediaType.APPLICATION_JSON)
4
class ApplicationInfoResource(
5
    @Inject private val applicationInfoService: ApplicationInfoService
6
) {
7

            
8
    @GET
9
    fun get(@QueryParam("request-to") requestTo: String?): Response =
10
        Response.ok(applicationInfoService.get(requestTo)).build()
11

            
12
    @GET
13
    @Path("/logo")
14
    @Produces("image/png")
15
    fun logo(): Response = Response.ok(applicationInfoService.getLogo()).build()
16
}



As you can see, beans are injected by @Inject annotation, and for each injected bean, you might specify a scope, for instance:
Kotlin
 




xxxxxxxxxx
1


 
1
@ApplicationScoped
2
class ApplicationInfoService(
3
    ...
4
) {
5
...
6
}



Creating of REST clients to other services is as simple as creating an interface using the proper JAX-RS and MicroProfile annotations:
Kotlin
 




xxxxxxxxxx
1
23


 
1
@ApplicationScoped
2
@Path("/")
3
interface ExternalServiceClient {
4
    @GET
5
    @Path("/application-info")
6
    @Produces("application/json")
7
    fun getApplicationInfo(): ApplicationInfo
8
}
9

           
10
@RegisterRestClient(baseUri = "http://helidon-service")
11
interface HelidonServiceClient : ExternalServiceClient
12

           
13
@RegisterRestClient(baseUri = "http://ktor-service")
14
interface KtorServiceClient : ExternalServiceClient
15

           
16
@RegisterRestClient(baseUri = "http://micronaut-service")
17
interface MicronautServiceClient : ExternalServiceClient
18

           
19
@RegisterRestClient(baseUri = "http://quarkus-service")
20
interface QuarkusServiceClient : ExternalServiceClient
21

           
22
@RegisterRestClient(baseUri = "http://spring-boot-service")
23
interface SpringBootServiceClient : ExternalServiceClient



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:
Kotlin
 




xxxxxxxxxx
1


 
1
@ApplicationScoped
2
class ConsulRegistrationBean(
3
    @Inject private val consulClient: ConsulClient
4
) {
5

           
6
    fun onStart(@Observes event: StartupEvent) {
7
        consulClient.register()
8
    }
9
}



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:
Kotlin
 




xxxxxxxxxx
1
21


 
1
@Provider
2
@ApplicationScoped
3
class ConsulFilter(
4
    @Inject private val consulClient: ConsulClient
5
) : ClientRequestFilter {
6

           
7
    override fun filter(requestContext: ClientRequestContext) {
8
        val serviceName = requestContext.uri.host
9
        val serviceInstance = consulClient.getServiceInstance(serviceName)
10
        val newUri: URI = URIBuilder(URI.create(requestContext.uri.toString()))
11
            .setHost(serviceInstance.address)
12
            .setPort(serviceInstance.port)
13
            .build()
14

           
15
        requestContext.uri = newUri
16
    }
17
}



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:

Kotlin
 




xxxxxxxxxx
1
16


 
1
@RestController
2
@RequestMapping(path = ["application-info"], produces = [MediaType.APPLICATION_JSON_VALUE])
3
class ApplicationInfoController(
4
    private val applicationInfoService: ApplicationInfoService
5
) {
6

            
7
    @GetMapping
8
    fun get(@RequestParam("request-to") requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo)
9

            
10
    @GetMapping(path = ["/logo"], produces = [MediaType.IMAGE_PNG_VALUE])
11
    fun getLogo(): ByteArray = applicationInfoService.getLogo()
12
}



The microservice is configured by a YAML file:
YAML
 




xxxxxxxxxx
1
16


 
1
spring:
2
  application:
3
    name: spring-boot-service
4

           
5
server:
6
  port: 8085
7

           
8
application-info:
9
  name: ${spring.application.name}
10
  framework:
11
    name: Spring Boot
12
    release-year: 2014



It is also possible to use properties files for configuration (more on Spring Boot config docs).

Launch

Before launching microservices, you need to install Consul and start the agent — for example, like this: consul agent -dev.

You can start microservices from:


  • IDE

    Users of IntelliJ IDEA may see something like the following:


    To launch Quarkus service, you need to start quarkusDev Gradle task.

  • Console

    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 http://localhost:8500/ui/dc1/services, you will see:

API Testing

As an example, the results of testing the API of Helidon service:


  • GET http://localhost:8081/application-info

  • JSON
     




    xxxxxxxxxx
    1


     
    1
    {
    2
      "name": "helidon-service",
    3
      "framework": {
    4
        "name": "Helidon SE",
    5
        "releaseYear": 2019
    6
      },
    7
      "requestedService": null
    8
    }


  • GET http://localhost:8081/application-info?request-to=ktor-service

  • JSON
     




    xxxxxxxxxx
    1
    15


     
    1
    {
    2
      "name": "helidon-service",
    3
      "framework": {
    4
        "name": "Helidon SE",
    5
        "releaseYear": 2019
    6
      },
    7
      "requestedService": {
    8
        "name": "ktor-service",
    9
        "framework": {
    10
              "name": "Ktor",
    11
              "releaseYear": 2018
    12
        },
    13
        "requestedService": null
    14
      }
    15
    }


  • GET http://localhost:8081/application-info/logo

    Returns an image

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 gradle.properties).

Artifact Size

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):

Microservice Artifact size, MB

Helidon service

17,3

Ktor service

22,4

Micronaut service

17,1

Quarkus service

24,4

Spring Boot service

45,2

Start Time

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:

Microservice Start time, seconds

Helidon service

2,0

Ktor service

1,5

Micronaut service

2,8

Quarkus service

1,9

Spring Boot service

10,7

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.

Memory Usage

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, this guide.

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.

Microservice

A Minimum amount of heap-memory, MB

For start a healthy service

For a load of 50 * 1000

For a load of 500 * 1000

Helidon service

11

9

11

Ktor service

13

11

15

Micronaut service

17

15

19

Quarkus service

13

17

21

Spring Boot service

18

19

23

It should be noted that all microservices use Netty HTTP server.

Conclusion

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.

Helidon

Standard Edition


  • Pros

    • Application parameters

    • Good results for all parameters       

    • No magic

    • Framework justified the developers' principle: for the created application only one annotation was needed (@JvmStatic — for Java-Kotlin interop)

  • Cons

    • Microframework

    • Some components necessary for industrial development are missing out of the box (for example, dependency injection and interaction with Service Discovery server)

MicroProfile

Microservice has not been implemented on this framework, so I will note only one point.


  • Pros

    • 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.)

Ktor


  • Pros

    • Lightweight — Allows you to add only those functions that are directly needed to perform the task

    • Application parameters — Good results for all parameters

  • Cons

    • “Sharpened” under Kotlin, that is, development in other languages can be impossible or is not worthwhile

    • Microframework — See Helidon SE

Additionally:

  • development concept

    • 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:

      • a problem with finding specialists

      • an increase in the time to perform tasks compared to the Spring Boot due to the need to explicitly configure the required functionality

    • 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

Micronaut

  • Pros

    • 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

  • Additionally:

    • The Micronaut for Spring project allows, among other things, changing the execution environment of the existing Spring Boot application to the Micronaut (with restrictions)

Quarkus

  • Pros

    • Eclipse MicroProfile implementation. See Helidon MP

    • Application parameters — Good results for all parameters

  • Additionally:

    • The framework provides compatibility layers for several Spring technologies: DI, Web, Security, Data JPA

Spring Boot

  • Pros

    • 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 and there is great educational resources available for your team.

  • Cons

    • 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:

  • Less developed ecosystem

  • Few specialists with experience with these technologies

  • Longer time of implementation of tasks

  • Unclear prospects

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

Spring Framework Spring Boot Framework microservice application Web Service Kotlin (programming language)

Published at DZone with permission of Roman Kudryashov. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Spring Boot - Microservice- JaxRS Jersey - HATEOAS - JerseyTest - Integration
  • Building Mancala Game in Microservices Using Spring Boot (Part 3: Web Client Microservice Implementation Using Vaadin)
  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • Component Tests for Spring Cloud Microservices

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!