Comparing Top Java/Kotlin Microservice Frameworks: Helidon, Ktor, Micronaut, Quarkus & Spring Boot
This article reviews five Java/Kotlin microservice frameworks—Helidon, Ktor, Micronaut, Quarkus, and Spring Boot—showing how to build heterogeneous services with Consul service discovery, detailing prerequisites, code examples, launch commands, API testing, and a side‑by‑side comparison of their strengths and weaknesses.
Introduction
In Java and Kotlin, besides using Spring Boot to create microservices, there are many alternative frameworks.
Frameworks
Name
Developer
Helidon SE
Oracle
Ktor
JetBrains
Micronaut
Object Computing
Quarkus
Red Hat
Spring Boot
Pivotal
Based on these microservice frameworks, five services are created and connected via Consul service discovery, forming a heterogeneous microservice architecture (MSA).
Technology Stack
JDK 13
Kotlin
Gradle (Kotlin DSL)
JUnit 5
Functional API
GET /application-info{?request-to=some-service-name}
GET /application-info/logo
Implementation Approach
Configuration via text files
Dependency injection
HTTP API
MSA Details
Service discovery using Consul (register and client‑side load balancing)
Build an uber‑JAR for each service
Prerequisites
JDK 13
Consul
Creating Applications
Generate new projects from any of the frameworks using a web starter, build tool, or IDE.
Helidon Service
Helidon offers two versions: SE and MicroProfile (MP). Both run as regular Java SE programs. Helidon MP implements Eclipse MicroProfile APIs; Helidon SE follows a "no magic" approach with minimal annotations.
Helidon SE is used for the microservice; Koin provides dependency injection.
object HelidonServiceApplication : KoinComponent {<br/> @JvmStatic<br/> fun main(args: Array<String>) {<br/> val startTime = System.currentTimeMillis()<br/> startKoin { modules(koinModule) }<br/> val applicationInfoService: ApplicationInfoService by inject()<br/> val consulClient: Consul by inject()<br/> val applicationInfoProperties: ApplicationInfoProperties by inject()<br/> val serviceName = applicationInfoProperties.name<br/> startServer(applicationInfoService, consulClient, serviceName, startTime)<br/> }<br/>}<br/><br/>fun startServer(<br/> applicationInfoService: ApplicationInfoService,<br/> consulClient: Consul,<br/> serviceName: String,<br/> startTime: Long<br/>): WebServer {<br/> val serverConfig = ServerConfiguration.create(Config.create().get("webserver"))<br/> val server: WebServer = WebServer.builder(createRouting(applicationInfoService))<br/> .config(serverConfig)<br/> .build()<br/> server.start().thenAccept { ws -><br/> val durationInMillis = System.currentTimeMillis() - startTime<br/> log.info("Startup completed in $durationInMillis ms. Service running at: http://localhost:" + ws.port())<br/> consulClient.agentClient().register(createConsulRegistration(serviceName, ws.port()))<br/> }<br/> return server<br/>}<br/>Routing configuration:
private fun createRouting(applicationInfoService: ApplicationInfoService) = Routing.builder()<br/> .register(JacksonSupport.create())<br/> .get("/application-info") { req, res -><br/> val requestTo = req.queryParams().first("request-to").orElse(null)<br/> res.status(Http.ResponseStatus.create(200)).send(applicationInfoService.get(requestTo))<br/> }<br/> .get("/application-info/logo") { req, res -><br/> res.headers().contentType(MediaType.create("image", "png"))<br/> .status(Http.ResponseStatus.create(200)).send(applicationInfoService.getLogo())<br/> }<br/> .error(Exception::class.java) { req, res, ex -><br/> log.error("Exception:", ex)<br/> res.status(Http.Status.INTERNAL_SERVER_ERROR_500).send()<br/> }<br/> .build()<br/>Configuration (HOCON):
webserver {<br/> port: 8081<br/>}<br/>application-info {<br/> name: "helidon-service"<br/> framework {<br/> name: "Helidon SE"<br/> release-year: 2019<br/> }<br/>}<br/>Ktor Service
Ktor is designed for Kotlin. Like Helidon SE, it lacks built‑in DI, so Koin is used before starting the server.
val koinModule = module {<br/> single { ApplicationInfoService(get(), get()) }<br/> single { ApplicationInfoProperties() }<br/> single { ServiceClient(get()) }<br/> single { Consul.builder().withUrl("https://localhost:8500").build() }<br/>}<br/><br/>fun main(args: Array<String>) {<br/> startKoin { modules(koinModule) }<br/> val server = embeddedServer(Netty, commandLineEnvironment(args))<br/> server.start(wait = true)<br/>}<br/>Ktor configuration (HOCON):
ktor {<br/> deployment {<br/> host = localhost<br/> port = 8082<br/> environment = prod<br/> autoreload = true<br/> watch = [io.heterogeneousmicroservices.ktorservice]<br/> }<br/> application {<br/> modules = [io.heterogeneousmicroservices.ktorservice.module.KtorServiceApplicationModuleKt.module]<br/> }<br/>}<br/><br/>application-info {<br/> name: "ktor-service"<br/> framework {<br/> name: "Ktor"<br/> release-year: 2018<br/> }<br/>}<br/>Ktor module definition:
fun Application.module() {<br/> val applicationInfoService: ApplicationInfoService by inject()<br/> if (!isTest()) {<br/> val consulClient: Consul by inject()<br/> registerInConsul(applicationInfoService.get(null).name, consulClient)<br/> }<br/> install(DefaultHeaders)<br/> install(Compression)<br/> install(CallLogging)<br/> install(ContentNegotiation) { jackson {} }<br/> routing {<br/> route("application-info") {<br/> get {<br/> val requestTo = call.parameters["request-to"]<br/> call.respond(applicationInfoService.get(requestTo))<br/> }<br/> static { resource("/logo", "logo.png") }<br/> }<br/> }<br/>}<br/>Micronaut Service
Micronaut, created by the Grails authors, supports Java, Kotlin, and Groovy with compile‑time DI, resulting in lower memory usage and faster startup.
object MicronautServiceApplication {<br/> @JvmStatic<br/> fun main(args: Array<String>) {<br/> Micronaut.build()<br/> .packages("io.heterogeneousmicroservices.micronautservice")<br/> .mainClass(MicronautServiceApplication.javaClass)<br/> .start()<br/> }<br/>}<br/>Controller example:
@Controller(value = "/application-info", consumes = [MediaType.APPLICATION_JSON], produces = [MediaType.APPLICATION_JSON])<br/>class ApplicationInfoController(private val applicationInfoService: ApplicationInfoService) {<br/> @Get fun get(requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo)<br/> @Get("/logo", produces = [MediaType.IMAGE_PNG]) fun getLogo(): ByteArray = applicationInfoService.getLogo()<br/>}<br/>Micronaut configuration (YAML):
micronaut:<br/> application:<br/> name: micronaut-service<br/> server:<br/> port: 8083<br/><br/>consul:<br/> client:<br/> registration:<br/> enabled: true<br/><br/>application-info:<br/> name: ${micronaut.application.name}<br/> framework:<br/> name: Micronaut<br/> release-year: 2018<br/>Quarkus Service
Quarkus targets cloud‑native environments with low memory consumption and fast startup, offering live reload out of the box.
@Path("/application-info")<br/>@Produces(MediaType.APPLICATION_JSON)<br/>@Consumes(MediaType.APPLICATION_JSON)<br/>class ApplicationInfoResource(@Inject private val applicationInfoService: ApplicationInfoService) {<br/> @GET fun get(@QueryParam("request-to") requestTo: String?): Response =<br/> Response.ok(applicationInfoService.get(requestTo)).build()<br/> @GET @Path("/logo") @Produces("image/png") fun logo(): Response =<br/> Response.ok(applicationInfoService.getLogo()).build()<br/>}<br/>Consul registration bean:
@ApplicationScoped<br/>class ConsulRegistrationBean(@Inject private val consulClient: ConsulClient) {<br/> fun onStart(@Observes event: StartupEvent) { consulClient.register() }<br/>}<br/>Spring Boot Service
Spring Boot simplifies development through auto‑configuration and the Spring ecosystem.
@RestController<br/>@RequestMapping(path = ["application-info"], produces = [MediaType.APPLICATION_JSON_VALUE])<br/>class ApplicationInfoController(private val applicationInfoService: ApplicationInfoService) {<br/> @GetMapping fun get(@RequestParam("request-to") requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo)<br/> @GetMapping(path = ["/logo"], produces = [MediaType.IMAGE_PNG_VALUE]) fun getLogo(): ByteArray = applicationInfoService.getLogo()<br/>}<br/>Spring Boot configuration (YAML):
spring:<br/> application:<br/> name: spring-boot-service<br/>server:<br/> port: 8085<br/>application-info:<br/> name: ${spring.application.name}<br/> framework:<br/> name: Spring Boot<br/> release-year: 2014<br/>Starting Services
Before starting services, install Consul and run a dev agent (e.g., consul agent -dev).
Launch each service with the following commands:
java -jar helidon-service/build/libs/helidon-service-all.jar<br/>java -jar ktor-service/build/libs/ktor-service-all.jar<br/>java -jar micronaut-service/build/libs/micronaut-service-all.jar<br/>java -jar quarkus-service/build/quarkus-service-1.0.0-runner.jar<br/>java -jar spring-boot-service/build/libs/spring-boot-service.jar<br/>After all services are running, visit http://localhost:8500/ui/dc1/services to view them.
API Testing
Example responses from the Helidon service:
{<br/> "name": "helidon-service",<br/> "framework": {"name": "Helidon SE", "releaseYear": 2019},<br/> "requestedService": null<br/>}<br/> {<br/> "name": "helidon-service",<br/> "framework": {"name": "Helidon SE", "releaseYear": 2019},<br/> "requestedService": {<br/> "name": "ktor-service",<br/> "framework": {"name": "Ktor", "releaseYear": 2018},<br/> "requestedService": null<br/> }<br/>}<br/>Tools such as Postman, IntelliJ IDEA HTTP client, or a browser can be used for testing.
Framework Comparison
Helidon SE
Pros: Application requires only a single annotation (@JvmStatic).
Cons: Some components (e.g., DI, service‑discovery integration) are not provided out of the box.
Helidon MP
Implements Eclipse MicroProfile, giving access to Java EE APIs and MicroProfile extensions.
Ktor
Pros: Lightweight; you add only the features you need.
Cons: Tied to Kotlin; fewer experts available; more configuration required compared to Spring Boot.
Micronaut
Pros: AOT compilation reduces startup time and memory usage; familiar to Spring developers.
Quarkus
Pros: Implements Eclipse MicroProfile; provides compatibility layers for Spring APIs (DI, Web, Security, Data JPA).
Spring Boot
Pros: Mature ecosystem, extensive documentation, and a large pool of experts; starters and auto‑configuration simplify development.
Cons: Many configuration parameters can make the application heavy; however, they can be trimmed with careful tuning.
Conclusion
All examined frameworks can deliver a simple HTTP API service that participates in a heterogeneous microservice architecture. Choosing the right one depends on trade‑offs such as startup time, memory footprint, ecosystem maturity, and developer familiarity.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
