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:

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=21 --kotlin
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 Quarkus REST
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 dataSource: DataSource,
) : GreetingRepository {
override fun getGreeting(): String =
dataSource.connection.use { connection ->
connection.prepareStatement("SELECT greeting FROM greetings ORDER BY random() LIMIT 1").use { statement ->
statement.executeQuery().use { resultSet ->
resultSet.next()
resultSet.getString("greeting")
}
}
}
}
- The
@ApplicationScopedannotation will make Quarkus create an instance at startup. - We inject a
DataSourceand use plain JDBC to retrieve one randomgreetingfrom thegreetingstable.
For this to work, we need some extra steps ...
Add the JDBC PostgreSQL extension:
quarkus extension add quarkus-jdbc-postgresql
Configure the datasource for dev and test profiles in application.yaml:
quarkus:
datasource:
db-kind: "postgresql"
devservices:
image-name: "postgres:14.5"
Configure it for prod profile in application-prod.yaml:
quarkus:
datasource:
db-kind: "postgresql"
username: "myuser"
password: "mypassword"
jdbc:
url: "jdbc:postgresql://${DB_HOST:localhost}:5432/mydb"
max-size: 20
Note that for dev and test profiles we use "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 add the extension:
quarkus extension add quarkus-flyway
And configure it in application.yaml:
quarkus:
flyway:
migrate-at-start: true
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,
@param:ConfigProperty(name = "greeting.name") private val name: String,
@param:ConfigProperty(name = "greeting.secret", defaultValue = "unknown") private val secret: String,
) {
@GET
@Produces(MediaType.TEXT_PLAIN)
fun hello() = "${repository.getGreeting()} 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 Jakarta REST 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 for JDK 21 via sdkman
sdk install java 21-graalce
sdk use java 21-graalce
# 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)