AutoKonfig

AutoKonfig

Kotlin configuration library with batteries included.

License Coverage Version

Features overview

  • Support for JSON, HOCON and Java properties config files
  • Loading config files from resources and remote URLs
  • Reading configuration from system properties and environment variables
  • Parsing command-line parameters
  • Merging properties loaded from multiple sources
  • Automatically finding config files
  • Type-safe properties
  • Support for default and optional values
  • Many useful property types, including dates (2020-02-02), times (10:15:30), durations (20s) and memory sizes (256 MB)
  • Type-specific parsing, a value of 1 can be the string "1", the integer 1, or the boolean true depending on which type is asked for
  • Collection types
  • 100% unit test coverage

Quick start

Gradle

implementation "dev.nohus:AutoKonfig:1.1.0"

Maven

<dependency>
    <groupId>dev.nohus</groupId>
    <artifactId>AutoKonfig</artifactId>
    <version>1.1.0</version>
</dependency>

The artifacts are available on Maven Central.

Simple example

Create a config file:

app.conf

host = nohus.dev
port = 80

Create variables for your properties:

Main.kt

fun main() {
    val host by StringSetting()
    val port by IntSetting()
    println("Host: $host, port: $port")
}

That's it! AutoKonfig automatically loaded your config file, because it had a well-known name. It knew which properties to load based on the variable names, and it mapped them to types based on the specified StringSetting and IntSetting delegates.

Tip

If you need simple global access to your configuration, declare an object Settings with your properties inside, and then use Settings.host or similar anywhere you need.

Property names

Property names are case insensitive, AutoKonfig will also figure out mappings between camelCase, snake_case, and kebab-case.

val httpPort by IntSetting()

All of the following keys would be matched to this property:

httpPort = 80
http-port = 80
http_port = 80
HTTP_PORT = 80

Info

If a config file actually contains multiple case versions of the same key, the exact match will be chosen first.

You can explicitly specify the key name if you don't want to rely on the variable name:

val port by IntSetting(name = "http-port")

It is also possible to read a property by name without declaring a variable:

AutoKonfig.getInt("http-port") // Returns 80

Similar get methods exist for all types.

Info

It is possible to retrieve all known properties without knowing their names by calling AutoKonfig.getAll(), which returns a Map<String, String>. This can be used to pass settings "as is" to other systems, but keep in mind that it is not type-safe.

Missing properties

Trying to read a property which is missing will result in an AutoKonfigException being thrown with an appropriate message. Declaring a variable delegated to a missing property will fail-fast. The variable is guaranteed to work at the point it's used.

Default values

Missing properties can use default values:

val port by IntSetting(default = 8080)

If the port property does not exist, the variable will return 8080 instead of throwing an exception.

Optional values

Missing properties can be allowed with optional types:

val port by OptionalIntSetting()

If the port property does not exist, the variable will be null instead of throwing an exception.

Setting groups

Properties can be organised in hierachical groups:

http {
    host = nohus.dev
    port = 80
}

They can be accessed with dot notation:

val port by IntSetting(name = "http.port")

The dot notation can also be used in config files. The following file is equivalent:

http.host = nohus.dev
http.port = 80

Variables can also be placed in Group objects, and AutoKonfig will figure out their location:

object Http : Group() {
    val host by StringSetting()
    val port by StringSetting()
}

You can then access Http.port in your code, and it will contain the expected value. The group objects can be freely nested, to represent the structure of your config files.

Info

Groups can be explicitly named: object Network : Group("http").

Loading configs

By default, config properties will be loaded from discovered files, Java system properties, and environment variables.

You can manually load more properties using the with family of methods:

AutoKonfig
    .withConfigs("app.conf", "server.conf") // Files by name
    .withConfig(file) // File by handle
    .withResourceConfig("internal.conf") // Resource file by name
    .withURLConfig(url) // Remote config file
    .withEnvironmentVariables() // Environment variables
    .withSystemProperties() // System properties

The methods can be chained, and properties from all sources will be merged together, with the latter overriding the former in case the keys are the same. There are also withProperties and withMap methods, that allow to you to manually insert a Java Properties object or a simple key-value map (Map<String, String>). This allows you to easily use the library with an external configuration source, like a database.

Tip

If you don't want to use properties loaded by default, call AutoKonfig.clear(). It can be chained just before your with methods.

Supported file types

AutoKonfig supports the HOCON format. JSON, Java properties and ini-style formats are valid HOCON, and so are also supported.

The autodiscovery of config files will attempt to read all files with the combination of names autokonfig, config, app, application, and extensions .conf, .json, .properties in the working directory of your program. For example: app.conf, config.json, etc. Of course, files with any extensions can be loaded manually as long as they have valid contents.

Command-line parameters

The library can also parse command-line parameters, just pass it the args array from your main method:

fun main(args: Array<String>) {
    AutoKonfig.withCommandLineArguments(args)
}

Short style (-a) and long style (--version) arguments are supported, both containing values and used as flags. For example:

[program] --verbose --duration 10s -l 12.5
val verbose by FlagSetting()
val duration by DurationSetting()
val length by FloatSetting(name = "l")

Info

All types work as normal on the command line, but remember that you need to quote values containing spaces in most shells: --body "Baltic Sea".

Property source tracing

You can verify where a key was loaded from by calling AutoKonfig.getKeySource("httpPort"), which will return a description of it's origin.

Examples:

Key "httpPort" was read as "HTTP_PORT" from config file at "path/to/file.conf"
Key "home" was read as "HOME" from environment variables
Key "verbose" was read from command line parameters
Key "missing" not found

AutoKonfig objects

The AutoKonfig class stores loaded properties and provides methods to access them. The library automatically creates a default one, and it is the one you are actually using when calling static methods on AutoKonfig. It is also implicitly used when declaring delegated variables. While this is convenient for most use cases, it is also possible to create your own instances of AutoKonfig:

val config = AutoKonfig()

It will start empty until you load some properties. To initialize it similarly to the default one, call withDefaults(), or withDiscoveredConfigs() to use just the config file autodiscovery.

Then you can use it as follows:

val httpPort by config.IntSetting()

or

val port = config.getInt("httpPort")

Tip

This is especially useful if you have multiple config files with the same structure, for example to configure multiple instances of an entity.