Comparing Five 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 services, configure Consul service discovery, run them, test their APIs, and compare their size, startup time, memory usage, and pros and cons.
Preface
Beyond Spring Boot, Java and Kotlin offer many alternatives for building microservices. This article creates five services using different frameworks and connects them via Consul service discovery, forming a heterogeneous microservice architecture (MSA).
Prerequisites
JDK 13
Consul
Creating Applications from Scratch
New projects can be generated with web starters or IDE tools.
Helidon Service
Helidon, originally an Oracle internal project, offers a simple SE version and a MicroProfile (MP) version. Helidon MP implements Eclipse MicroProfile APIs; Helidon SE follows a “no magic” approach with minimal annotations and uses Koin for dependency injection.
object HelidonServiceApplication : KoinComponent {
@JvmStatic
fun main(args: Array<String>) {
val startTime = System.currentTimeMillis()
startKoin { modules(koinModule) }
val applicationInfoService: ApplicationInfoService by inject()
val consulClient: Consul by inject()
val applicationInfoProperties: ApplicationInfoProperties by inject()
val serviceName = applicationInfoProperties.name
startServer(applicationInfoService, consulClient, serviceName, startTime)
}
}
fun startServer(
applicationInfoService: ApplicationInfoService,
consulClient: Consul,
serviceName: String,
startTime: Long
): WebServer {
val serverConfig = ServerConfiguration.create(Config.create().get("webserver"))
val server: WebServer = WebServer.builder(createRouting(applicationInfoService))
.config(serverConfig)
.build()
server.start().thenAccept { ws ->
val durationInMillis = System.currentTimeMillis() - startTime
log.info("Startup completed in $durationInMillis ms. Service running at: http://localhost:" + ws.port())
consulClient.agentClient().register(createConsulRegistration(serviceName, ws.port()))
}
return server
}Routing configuration:
private fun createRouting(applicationInfoService: ApplicationInfoService) = Routing.builder()
.register(JacksonSupport.create())
.get("/application-info") { req, res ->
val requestTo = req.queryParams().first("request-to").orElse(null)
res.status(Http.ResponseStatus.create(200)).send(applicationInfoService.get(requestTo))
}
.get("/application-info/logo") { req, res ->
res.headers().contentType(MediaType.create("image", "png"))
.status(Http.ResponseStatus.create(200))
.send(applicationInfoService.getLogo())
}
.error(Exception::class.java) { req, res, ex ->
log.error("Exception:", ex)
res.status(Http.Status.INTERNAL_SERVER_ERROR_500).send()
}
.build()Configuration uses HOCON format, but JSON, YAML, or properties are also supported.
Ktor Service
Ktor is a Kotlin‑first framework without built‑in DI; Koin is used for dependency injection before starting the server.
val koinModule = module {
single { ApplicationInfoService(get(), get()) }
single { ApplicationInfoProperties() }
single { ServiceClient(get()) }
single { Consul.builder().withUrl("https://localhost:8500").build() }
}
fun main(args: Array<String>) {
startKoin { modules(koinModule) }
val server = embeddedServer(Netty, commandLineEnvironment(args))
server.start(wait = true)
}Configuration (HOCON) example:
ktor {
deployment {
host = localhost
port = 8082
environment = prod
autoreload = true
watch = [io.heterogeneousmicroservices.ktorservice]
}
application {
modules = [io.heterogeneousmicroservices.ktorservice.module.KtorServiceApplicationModuleKt.module]
}
}
application-info {
name = "ktor-service"
framework {
name = "Ktor"
release-year = 2018
}
}Micronaut Service
Micronaut, created by the Grails author, supports Java, Kotlin, and Groovy with compile‑time dependency injection, resulting in lower memory usage and faster startup compared to Spring Boot.
object MicronautServiceApplication {
@JvmStatic
fun main(args: Array<String>) {
Micronaut.build()
.packages("io.heterogeneousmicroservices.micronautservice")
.mainClass(MicronautServiceApplication.javaClass)
.start()
}
}Controller example:
@Controller(
value = "/application-info",
consumes = [MediaType.APPLICATION_JSON],
produces = [MediaType.APPLICATION_JSON]
)
class ApplicationInfoController(
private val applicationInfoService: ApplicationInfoService
) {
@Get
fun get(requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo)
@Get("/logo", produces = [MediaType.IMAGE_PNG])
fun getLogo(): ByteArray = applicationInfoService.getLogo()
}Configuration (YAML) example:
micronaut:
application:
name: micronaut-service
server:
port: 8083
consul:
client:
registration:
enabled: true
application-info:
name: ${micronaut.application.name}
framework:
name: Micronaut
release-year: 2018Quarkus Service
Quarkus targets cloud‑native environments, offering low memory consumption and fast startup, with built‑in live reload. It implements Eclipse MicroProfile and provides Spring compatibility layers.
@Path("/application-info")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
class ApplicationInfoResource(
@Inject private val applicationInfoService: ApplicationInfoService
) {
@GET
fun get(@QueryParam("request-to") requestTo: String?): Response =
Response.ok(applicationInfoService.get(requestTo)).build()
@GET
@Path("/logo")
@Produces("image/png")
fun logo(): Response = Response.ok(applicationInfoService.getLogo()).build()
}Quarkus does not include built‑in service‑discovery for Consul/Eureka; the Consul client is used manually as in other frameworks.
Spring Boot Service
Spring Boot leverages auto‑configuration to simplify development.
@RestController
@RequestMapping(path = ["application-info"], produces = [MediaType.APPLICATION_JSON_VALUE])
class ApplicationInfoController(
private val applicationInfoService: ApplicationInfoService
) {
@GetMapping
fun get(@RequestParam("request-to") requestTo: String?): ApplicationInfo =
applicationInfoService.get(requestTo)
@GetMapping(path = ["/logo"], produces = [MediaType.IMAGE_PNG_VALUE])
fun getLogo(): ByteArray = applicationInfoService.getLogo()
}Configuration (YAML):
spring:
application:
name: spring-boot-service
server:
port: 8085
application-info:
name: ${spring.application.name}
framework:
name: Spring Boot
release-year: 2014Starting the Microservices
Install Consul and start its agent (e.g., consul agent -dev). Then launch each service with its respective JAR:
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.jarAfter all services are running, open http://localhost:8500/ui/dc1/services to view the registered services.
API Testing
Example responses from the Helidon service:
{
"name": "helidon-service",
"framework": {"name": "Helidon SE", "releaseYear": 2019},
"requestedService": null
} {
"name": "helidon-service",
"framework": {"name": "Helidon SE", "releaseYear": 2019},
"requestedService": {
"name": "ktor-service",
"framework": {"name": "Ktor", "releaseYear": 2018},
"requestedService": null
}
}Tools such as Postman, IntelliJ HTTP client, or a browser can be used for testing.
Comparison of Microservice Frameworks
Key comparison points include program size, startup time, and memory usage. Spring Boot’s uber‑JAR is larger due to starters, while frameworks like Helidon SE produce smaller binaries. Startup times vary; Micronaut’s AOT compilation reduces both startup latency and memory consumption. All services use Netty as the HTTP server.
Conclusion
All examined frameworks can provide a simple HTTP API service within an MSA. Choosing the right one depends on trade‑offs such as startup speed, memory footprint, ecosystem maturity, and available expertise.
Helidon SE
Pros: Minimal annotation required.
Cons: Lacks out‑of‑the‑box DI and service‑discovery integration.
Helidon MP
Pros: Implements Eclipse MicroProfile; can switch to other MicroProfile runtimes.
Ktor
Pros: Lightweight, only adds needed features.
Cons: Kotlin‑only; fewer experts; more manual configuration than Spring Boot.
Micronaut
Pros: AOT reduces startup time and memory; familiar to Spring developers.
Quarkus
Pros: Eclipse MicroProfile implementation; Spring compatibility layer.
Spring Boot
Pros: Mature ecosystem, extensive documentation, many experts.
Cons: Larger binaries and more configuration parameters; can be optimized with Spring Fu.
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.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
