Code as Configuration in Kotlin

James
The Startup
Published in
4 min readDec 30, 2020

--

Even for a small application, it will often become desirable to change settings when the app runs rather than when it is compiled. A configuration file full of the various settings for the application is not uncommon in this scenario, where changes to the settings can be made and the app restarted with the new settings — the only question is what format should this settings file be?

JSON and YAML are both popular choices, as are Java Properties Files and sometimes INI files and other application-specific text formats. They all have their pros and cons, but all fall in the same category— they are a fixed list of properties and their values. Some may be easier for a person to read than others, some may support nested lists or sets where others don’t, but in the end they’re mostly equivalent. I’d like to offer a different approach — we use Kotlin as our configuration language.

At first glance this might seem like a strange idea, but it has merits that the config-file formats do not

  1. It’s already the language we’re working in. While it might not seem like much, the mental gear change of changing language when you change file adds up. When you might be working on a frontend in HTML/CSS/JS, an iOS app in Swift, and a backend service in Kotlin; adding YAML to the developers’ mental load for configuration is another straw on an already-burdened camel.
  2. It can contain logic. The static text formats cannot do this. If you want separate development and production settings, you have two different files. If the majority of the settings are the same, you possible have a way to include a common file, or it’s part of your build process to merge them. A Kotlin config file can just have an if statement around the settings that change.
  3. IDE support. Your IDE can help out more with your syntax errors and autocompletion when you use Kotlin as your config language.
  4. Extensibility — if you’re already loading Kotlin as config, there’s no reason you can’t dynamically load other parts of your app.

This is not a brand new idea. Scripting languages often take this approach, Lua being a particular proponent of it. However, for a compiled language it’s less obvious how we would go about it.

It turns out that Kotlin supports the standard that Java sets out for running scripts and scripting languages — it can be used as a scripting language itself. And doing so is fairly simple.

First, we’re going to need some extra libraries to help, and a simple piece of configuration in our application. Using our build system (Gradle, Maven, Ant, IntelliJ, whatever you use), we need to add two packages: kotlin-scripting-jsr223 and kotlin-script-runtime. An example in gradle would be¹:

We also need to add a file to tell the JVM what scripting languages we’re supporting. Assuming you have a fairly standard project layout (i.e. your kotlin code all lives in packages in src/main/kotlin/... ) then we need to create the file
src/main/META-INF/services/javax.script.ScriptEngineFactory which should have only one line in it: org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory

With these in place, we can now run our configuration file when the application starts. We’ll work through an example. Here’s our main file:

Fairly simple, we define a Config object with a sensible default value of name and we print the name in the main method. We now add our config file to the directory our project launches from (usually the root of your project)

Notice the file name is config.kts — a .kt file is a normal Kotlin file, .kts is a Kotlin Script file. As far as scripts go it’s simple, we just overwrite the name variable.

On its own, we haven’t told the application how to load the file. we need to extend out main file:

When we run this code, we should see “Script File” output to our console. If we change the content of the script file’s name and run again without recompiling, we should see our changes printed to the console instead.

What if we want to change our output based on the environment? Suppose we have an environment variable MY_APP_MODE which could be set to "development" or "production" . If it’s unset, we want to use development mode. We can read the environment variable in our script, and use its value to choose what we set the config to:

And that’s all there is to it.

  1. I’ve used version 1.4.10 here, versions after 1.4.20 have a rather alarming warning about an illegal reflection access. It doesn’t appear to impact the functionality but I’m sticking with the earlier version until that warning is resolved

--

--