This is a demo inspired by @antonarhipov's Top 5 Server-Side Frameworks for Kotlin in 2022 @ Kotlin by JetBrains where, spoiler alert, the author shares this top 5 list:
🥇 Spring Boot
🥈 Quarkus
🥉 Micronaut
🏅 Ktor
🏅 http4k
I have a lot of experience in Spring Boot, so I wanted to take a look at the other ones 😜

To do so we will create a simple application with each one of these frameworks, implementing the following scenario:

rogervinas
/
top-5-server-side-kotlin-frameworks-2022
⭐ Top 5 Server-Side Frameworks for Kotlin in 2022
This post will describe the step-by-step Quarkus implementation, you can check the other ones in this series too.
To begin with you can follow the Quarkus quick start. You will see that there is a quarkus command line (easily installable via sdkman) to create an application choosing Gradle or Maven as the build tool. Once the application is created we can use both quarkus command line or gradlew/mvnw.
You can also check Creating your first application as well as all the other guides.
To create a simple application with a reactive REST endpoint:
sdk install quarkus
quarkus create app org.rogervinas:quarkus-app \
--gradle-kotlin-dsl --java=17 --kotlin \
--extension='kotlin,resteasy-reactive-jackson'
A Gradle project will be created with the following:
- src/main/docker: a few Dockerfiles with different options to create the application docker image.
-
src/main: main sources with a template
GreetingResourceimplementing a REST endpoint on/hello. - src/test: test sources with an integration test of the endpoint.
- src/native-test: test sources with the same integration test of the endpoint but starting the application using the docker native image.
Just run it once to check everything is ok:
quarkus dev
And make a request to the endpoint:
curl http://localhost:8080/hello
Hello from RESTEasy Reactive
We can remove the src/main/resources/META-INF directory containing some HTML files that we will not need.
Implementation
YAML configuration
To use yaml configuration files we need to add this extension:
quarkus extension add quarkus-config-yaml
And then rename application.properties to application.yaml and add our first configuration property:
greeting:
name: "Bitelchus"
Note that in Quarkus we have these Default profiles (similar to "profiles" in Spring Boot):
- dev - Activated when in development mode (i.e. quarkus:dev).
- test - Activated when running tests.
- prod - The default profile when not running in development or test mode.
So we will create a application-prod.yaml file to put there all the production configuration properties.
More documentation about configuration sources at Configuration Reference guide.
GreetingRepository
We will create a GreetingRepository:
interface GreetingRepository {
fun getGreeting(): String
}
@ApplicationScoped
class GreetingJdbcRepository(
private val client: PgPool
): GreetingRepository {
override fun getGreeting(): String = client
.query(
"""
SELECT greeting
FROM greetings
ORDER BY random() LIMIT 1
""".trimIdent()
)
.executeAndAwait()
.map { r -> r.get(String::class.java, "greeting") }
.first()
}
- The
@ApplicationScopedannotation will make Quarkus to create an instance at startup. - We inject the Reactive SQL Client.
- We use
queryand that SQL to retrieve one randomgreetingfrom thegreetingstable.
For this to work, we need some extra steps ...
Add the Reactive SQL Client extension:
quarkus extension add quarkus-reactive-pg-client
Configure it for dev and test profiles in application.yaml:
quarkus:
datasource:
devservices:
image-name: "postgres:14.5"
Configure it for prod profile in application-prod.yaml:
quarkus:
datasource:
db-kind: "postgresql"
username: "myuser"
password: "mypassword"
reactive:
url: "postgresql://${DB_HOST:localhost}:5432/mydb"
max-size: 20
Note that for dev and test profiles we just use something called "Dev Services", meaning it will automatically start containers and configure the application to use them. You can check the Dev Services Overview and Dev Services for Databases documentation.
To enable Flyway we need to add these dependencies manually (apparently there is no extension):
implementation("io.quarkus:quarkus-flyway")
implementation("io.quarkus:quarkus-jdbc-postgresql")
And, as it seems that we cannot use the same reactive datasource, we will have to configure the standard one:
-
application.yaml
quarkus:
flyway:
migrate-at-start: true
-
application-prod.yaml
quarkus:
datasource:
jdbc:
url: "jdbc:postgresql://${DB_HOST:localhost}:5432/mydb"
max-size: 20
So quarkus.datasource.jdbc will be used by Flyway and quarkus.datasource.reactive by the application.
Finally, Flyway migrations under src/main/resources/db/migration to create and populate greetings table.
GreetingController
We will rename the generated GreetingResource class to GreetingController, so it looks like the Spring Boot one:
@Path("/hello")
class GreetingController(
private val repository: GreetingRepository,
@ConfigProperty(name = "greeting.name")
private val name: String,
@ConfigProperty(
name = "greeting.secret",
defaultValue = "unknown"
)
private val secret: String
) {
@GET
@Produces(MediaType.TEXT_PLAIN)
fun hello() {
val greeting = repository.getGreeting()
return "$greeting my name is $name and my secret is $secret"
}
}
- We can inject dependencies via constructor and configuration properties using
@ConfigPropertyannotation. - We expect to get
greeting.secretfrom Vault, that is why we configureunknownas its default value, so it does not fail until we configure Vault properly. - Everything is pretty similar to Spring Boot. Note that it uses standard JAX-RS annotations which is also possible in Spring Boot (but not by default).
Vault configuration
Following the Using HashiCorp Vault guide we add the extension:
quarkus extension add vault
For dev and test profiles we configure Vault "Dev Service" in application.yaml:
quarkus:
vault:
secret-config-kv-path: "myapp"
devservices:
image-name: "vault:1.12.1"
init-commands:
- "kv put secret/myapp greeting.secret=watermelon"
Note that here we can use these init-commands to populate Vault 🥹
For prod profile we configure Vault in application-prod.yaml:
quarkus:
vault:
url: "http://${VAULT_HOST:localhost}:8200"
authentication:
client-token: "mytoken"
Testing the endpoint
We rename the original GreetingResourceTest to GreetingControllerTest and we modify it this way:
@QuarkusTest
@TestHTTPEndpoint(GreetingController::class)
class GreetingControllerTest {
@InjectMock
private lateinit var repository: GreetingRepository
@Test
fun `should say hello`() {
doReturn("Hello").`when`(repository).getGreeting()
`when`()
.get()
.then()
.statusCode(200)
.body(
`is`(
"Hello my name is Bitelchus and my secret is watermelon"
)
)
}
}
-
@QuarkusTestwill start all "Dev Services", despite the database not being used 🤷 - We mock the repository with
@InjectMock. - We use RestAssured to test the endpoint.
- As this test uses Vault, the secret should be
watermelon.
Testing the application
We can test the whole application this way:
@QuarkusTest
class GreetingApplicationTest {
@Test
fun `should say hello`() {
given()
.`when`().get("/hello")
.then()
.statusCode(200)
.body(
matchesPattern(
".+ my name is Bitelchus and my secret is watermelon"
)
)
}
}
-
@QuarkusTestwill start all "Dev Services", now all of them are being used. - We use RestAssured to test the endpoint.
- We use pattern matching to check the greeting, as it is random.
- As this test uses Vault, the secret should be
watermelon.
Test
./gradlew test
Run
# Start Application with "Dev Services"
quarkus dev
# Make requests
curl http://localhost:8080/hello
Build a fatjar and run it
# Build fatjar
quarkus build
# Start Vault and Database
docker compose up -d vault vault-cli db
# Start Application
java -jar build/quarkus-app/quarkus-run.jar
# Make requests
curl http://localhost:8080/hello
# Stop Application with control-c
# Stop all containers
docker compose down
Build a docker image and run it
# Build docker image
quarkus build
docker build -f src/main/docker/Dockerfile.jvm -t quarkus-app .
# Start Vault and Database
docker compose up -d vault vault-cli db
# Start Application
docker compose --profile quarkus up -d
# Make requests
curl http://localhost:8080/hello
# Stop all containers
docker compose --profile quarkus down
docker compose down
Build a native executable and run it
Following Build a Native Executable:
# Install GraalVM via sdkman
sdk install java 22.3.r19-grl
sdk default java 22.3.r19-grl
export GRAALVM_HOME=$JAVA_HOME
# Install the native-image
gu install native-image
# Build native executable
quarkus build --native
# Start Vault and Database
docker compose up -d vault vault-cli db
# Start Application using native executable
./build/quarkus-app-1.0.0-SNAPSHOT-runner
# Make requests
curl http://localhost:8080/hello
# Stop Application with control-c
# Stop all containers
docker compose down
That's it! Happy coding! 💙
Top comments (0)