Building a native CLI with Kotlin and GraalVM
Tags: kotlin, showdev, tutorial
Published: 2019-04-08 15:24:17

---
title: Building a native CLI with Kotlin and GraalVM
published: true
description: Build a formula one command line app with kotlin and GraalVM
tags: kotlin, showdev, tutorial
---

#Building native code using java

[GraalVM](https://www.graalvm.org/) is an exciting new VM released by Oracle that enables creation of polyglot apps. Perhaps one of the most exciting new features is the `native-image` tool that compiles your bytecode into a single binary that can be executed on the target runtime without the need of a JVM.

But because it's now packaged as a single binary it means it also does not need to create a JVM in first place, and memory usage and the `warmup` period associated with JVM processes are long gone.

Just to give you an idea of what we are talking about, the app we will build today had the following results (java vs native versions)

```
Java: 0.97s user 0.10s system 131% cpu 0.808 total

Binary: 0.01s user 0.01s system 8% cpu 0.232 total

``` 

# Command Line apps the missing use case of java

Now, there are a couple of frameworks out there like [Quarkus](https://quarkus.io/) and [Micronaut](https://micronaut.io/) that provides an easy way to migrate server side apps into native ones.

That's all great, but IMHO server side has never been the issue with java, once a JVM is `warmed up` and serving thousands of requests, it's very hard to beat it with the node/go versions. It's fast, it is stable, and once you hit a given threshold of users, the memory footprint is not that bad.

CLI apps on the other hand, were always a weak spot for java. No one wants to download a jar file, install (the right version) java, just to execute a few commands.

More often than not our teams need to write a CLI to integrate something, and java/kotlin have never been the choice. This is where go became king, and hopefully now with [GraalVM](https://www.graalvm.org/) things can change a bit.

# So what are we building today?

I wanted to build a CLI to wrap a REST service. A lot of CLIs today work in this fashion: kubectl, gcloud, azure... So it seemed like a good start.

Our CLI app, which we will call `f1`, will be a wrapper for the [Ergast motor racing API](https://ergast.com/mrd/).

# Requirements, limitations, where does it hurt?

Converting a java app into a native app can be a painful experience depending on what features of the JVM you use. Specially reflection, read this [section on reflection](https://github.com/oracle/graal/blob/master/substratevm/REFLECTION.md) if you want to understand more about it.

I tried my best to avoid reflection based frameworks when writing this article, it gets specially painful when you have to deal with JSON and kotlin data classes. 

Dependencies:

* [Clikt](https://ajalt.github.io/clikt) - Kotlin based CLI parser and framework. No annotations, no reflections, less to worry when building your native app
* [Fuel](https://fuel.gitbook.io/documentation/) - Kotlin http client library, with no reflections (like retrofit2) and others.
* [Gson] - Google JSON library, I chose to write specific SerDes classes, it wasn't that bad, if you go the reflection route you may need to append classes to the `reflect-config.json`

# Let's build it

All the code can be found on this [github repo](https://github.com/viniciusccarvalho/f1-kotlin).

Start a new gradle kotlin project. I usually go with IntelliJ IDEA's new project wizard.

I rather create a fat jar so make sure your `build.gradle` looks something like this:

```
buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.github.jengelman.gradle.plugins:shadow:4.0.3'
    }
}
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'application'
apply plugin: "com.github.johnrengelman.shadow"

group 'io.igx.kotlin'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8


mainClassName = "io.igx.kotlin.ApplicationKt"

repositories {
    mavenCentral()
    jcenter()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation 'com.github.ajalt:clikt:1.7.0'
    implementation 'com.github.kittinunf.fuel:fuel-gson:2.0.1'
    implementation 'de.vandermeer:asciitable:0.3.2'
    testCompile group: 'junit', name: 'junit', version: '4.12'
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

```

Modify your `Application.kt` file to have a simple print on it:

```kotlin
fun main(args: Array<String>) {
    println("Hello Native")
}

```

___

**Important** : Make sure you have the GraalVM installed, and that $GRAALVM_BIN is the folder where your install binaries are located.
___

Build your app `./gradlew build` and now generate the native image by invoking the following command

```
$GRAALVM_BIN/native-image --report-unsupported-elements-at-runtime  -jar build/libs/f1-1.0-SNAPSHOT-all.jar f1 --no-server
```

You can check what each of those flags mean [here](https://www.graalvm.org/docs/reference-manual/aot-compilation/)

After the native-image tool finishes you should have an `f1` binary. Just invoke it: `./f1` and you get a nice `Hello Native` output.

## Creating the commands

What we need now is some commands that we want to translate into REST calls. Clikt we will create a main command `F1` and nest two subcommands: `Drivers` and `Circuits`.

Create a `Commands.kt` file and add the following code:

```kotlin

class F1 : CliktCommand() {
    override fun run() = Unit
}

class DriverCommands : CliktCommand(name = "drivers") {

    val season: Int by option(help = "Season year").int().default(LocalDate.now().year)

    override fun run() {
       println("Fetching drivers of $season season")
    }

}

class CircuitCommands : CliktCommand(name="circuits") {
    val season: Int by option(help = "Season year").int().default(LocalDate.now().year)

    override fun run() {
       println("Fetching circuits of $season season")
    }

}

Change your `Application.kt` main method to:


```kotlin
fun main(args: Array<String>) {
   F1().subcommands(DriverCommands(), CircuitCommands()).main(args)
}
```

Build the whole thing with `gradle` and run the `native-image` command again.


```

Try running `./f1` now and you will be greet with some nice default help message from clikt:

```
Usage: f1 [OPTIONS] COMMAND [ARGS]...

Options:
  -h, --help  Show this message and exit

Commands:
  drivers
  circuits
```

As you can see we have two registered commands, and running help on those:

```
./f1 drivers --help
Usage: f1 drivers [OPTIONS]

Options:
  --season INT  Season year
  -h, --help    Show this message and exit
```

Ok so we now have our skeleton for our CLI app, let's just add some content to it.

## Handling JSON

As I said before, reflection is a bit painful with GraalVM. So I went for a method of writing my own `JsonDeserializer` for each of the domain objects of the remote API.

The ergast API has another problem, the way they represent `wrapped` json is not really friendly to java parsing (they pretty much mimic their XML representation), so customization for handling those wrappers would be needed anyways.

I've created a `Domain.kt` file where I put all the serialization logic:

```kotlin
data class Response(val series: String, val limit: Int, val offset:Int, val total:Int)
data class Driver(val driverId: String, val code: String?, val url: String? = "", val givenName: String, val familyName: String, val dateOfBirth: String?, val nationality: String?)
data class DriverResponse(val response: Response, val season: Int, val drivers: List<Driver>)

data class Location(val lat: Float?, val lon: Float?, val locality: String?, val country: String?)
data class Circuit(val circuitId: String, val url: String?, val name: String, val location: Location)
data class CircuitResponse(val response: Response, val season: Int, val circuits: List<Circuit>)

class CircuitDeserializer : JsonDeserializer<Circuit>{
    override fun deserialize(element: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Circuit {
        val json = element as JsonObject
        val jsonLocation = json.getAsJsonObject("Location")
        val location = Location(jsonLocation.get("lat")?.asFloat, jsonLocation.get("long")?.asFloat, jsonLocation.get("locality")?.asString, jsonLocation.get("country")?.asString)
        return Circuit(json.get("circuitId").asString, json.get("url")?.asString, json.get("circuitName").asString, location)
    }
}

class DriverDeserializer : JsonDeserializer<Driver> {
    override fun deserialize(element: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Driver {
        val json = element as JsonObject
        return Driver(json.get("driverId").asString, json.get("code")?.asString, json.get("url")?.asString, json.get("givenName").asString, json.get("familyName").asString, json.get("dateOfBirth")?.asString, json.get("nationality")?.asString)
    }

}

class CircuitResponseDeserializer : JsonDeserializer<CircuitResponse> {
    override fun deserialize(
        element: JsonElement?,
        typeOfT: Type?,
        context: JsonDeserializationContext?
    ): CircuitResponse {
        val wrapper = element as JsonObject
        val json = wrapper.getAsJsonObject("MRData")
        val response = json.toResponse()
        val table = json.get("CircuitTable").asJsonObject
        val season = table.get("season").asInt
        val circuitType = object : TypeToken<List<Circuit>>() {}.type
        return CircuitResponse(response, season, context!!.deserialize(table.get("Circuits").asJsonArray, circuitType))

    }

}

class DriverResponseDeserializer : JsonDeserializer<DriverResponse> {

    override fun deserialize(element:  JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): DriverResponse {

        val wrapper = element as JsonObject
        val json = wrapper.getAsJsonObject("MRData")
        val response = json.toResponse()

        val table = json.get("DriverTable").asJsonObject
        val season = table.get("season").asInt
        val driversType = object : TypeToken<List<Driver>>() {}.type

        return DriverResponse(response, season, context!!.deserialize(table.get("Drivers").asJsonArray, driversType))
    }

}

fun JsonObject.toResponse() : Response {
    return Response(this.get("series").asString, this.get("limit").asInt, this.get("offset").asInt, this.get("total").asInt)
}

```

And the final version of your `Commands.kt` should look like this:

```kotlin

class DriverCommands : CliktCommand(name = "drivers") {

    val season: Int by option(help = "Season year").int().default(LocalDate.now().year)

    override fun run() {
        val gson = GsonBuilder()
            .registerTypeAdapter(DriverResponse::class.java, DriverResponseDeserializer())
            .registerTypeAdapter(Driver::class.java, DriverDeserializer())
            .create()
        val responseString = Fuel.get("http://ergast.com/api/f1/$season/drivers.json").responseString().third.get()
        val driverResponse = gson.fromJson<DriverResponse>(responseString, DriverResponse::class.java)
        val table = AsciiTable()
        table.addRule()
        table.addRow(null, null, "$season season drivers").setTextAlignment(TextAlignment.CENTER)
        table.addRule()
        table.addRow("Driver name", "Date birth", "Nationality").setTextAlignment(TextAlignment.CENTER)
        table.addRule()
        driverResponse.drivers.forEach {driver ->
            table.addRow("${driver.givenName} ${driver.familyName}", driver.dateOfBirth, driver.nationality).setTextAlignment(TextAlignment.CENTER)

            table.addRule()
        }
        println(table.render())
    }

}

class CircuitCommands : CliktCommand(name="circuits") {
    val season: Int by option(help = "Season year").int().default(LocalDate.now().year)

    override fun run() {
        val gson = GsonBuilder()
            .registerTypeAdapter(CircuitResponse::class.java, CircuitResponseDeserializer())
            .registerTypeAdapter(Circuit::class.java, CircuitDeserializer())
            .create()
        val table = AsciiTable()
        table.addRule()
        table.addRow(null, null, "$season season circuits").setTextAlignment(TextAlignment.CENTER)
        table.addRule()
        table.addRow("Circuit", "Location", "Country").setTextAlignment(TextAlignment.CENTER)
        table.addRule()
        val responseString = Fuel.get("http://ergast.com/api/f1/$season/circuits.json").responseString().third.get()
        val circuitResponse = gson.fromJson<CircuitResponse>(responseString, CircuitResponse::class.java)
        circuitResponse.circuits.forEach {circuit ->
            table.addRow(circuit.name, circuit.location.locality, circuit.location.country).setTextAlignment(TextAlignment.CENTER)
            table.addRule()
        }
        println(table.render())
    }

}

```

1. First we prepare our `Gson` instance by registering the proper adapters
2. The `AsciiTable` is just a nice way to present the results
3. We get the response as string using `Fuel` and then map it to our `Response` type
4. Print the table.

Now build the project again using `gradle` but this time you need to modify the `native-image` command to enable the `http` protocol (disabled by default):

```
$GRAALVM_BIN/native-image  --report-unsupported-elements-at-runtime  -jar build/libs/f1-1.0-SNAPSHOT-all.jar f1 --enable-url-protocols=http --no-server
```

After building it try running the `drivers` command:

```

f1 ./f1 drivers
┌──────────────────────────────────────────────────────────────────────────────┐
│                             2019 season drivers                              │
├──────────────────────────┬─────────────────────────┬─────────────────────────┤
│       Driver name        │       Date birth        │       Nationality       │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│     Alexander Albon      │       1996-03-23        │          Thai           │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│     Valtteri Bottas      │       1989-08-28        │         Finnish         │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│       Pierre Gasly       │       1996-02-07        │         French          │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│    Antonio Giovinazzi    │       1993-12-14        │         Italian         │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│     Romain Grosjean      │       1986-04-17        │         French          │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│      Lewis Hamilton      │       1985-01-07        │         British         │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│     Nico Hülkenberg      │       1987-08-19        │         German          │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│      Robert Kubica       │       1984-12-07        │         Polish          │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│       Daniil Kvyat       │       1994-04-26        │         Russian         │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│     Charles Leclerc      │       1997-10-16        │       Monegasque        │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│     Kevin Magnussen      │       1992-10-05        │         Danish          │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│       Lando Norris       │       1999-11-13        │         British         │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│       Sergio Pérez       │       1990-01-26        │         Mexican         │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│      Kimi Räikkönen      │       1979-10-17        │         Finnish         │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│     Daniel Ricciardo     │       1989-07-01        │       Australian        │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│      George Russell      │       1998-02-15        │         British         │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│       Carlos Sainz       │       1994-09-01        │         Spanish         │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│       Lance Stroll       │       1998-10-29        │        Canadian         │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│      Max Verstappen      │       1997-09-30        │          Dutch          │
├──────────────────────────┼─────────────────────────┼─────────────────────────┤
│     Sebastian Vettel     │       1987-07-03        │         German          │
└──────────────────────────┴─────────────────────────┴─────────────────────────┘
```

# Closing thoughts

I'm truly excited to see that we can now build amazing CLI tooling with Java. I hope this post got you excited too. No need to split your team, or to ask them to learn a new language to write some cool tooling for your internal apis.

Happy coding!