Previously we covered environment aware configurations for building a simple default config with overrides for local development and production. Let's dive a little deeper and show examples with some of the more useful features built into Typesafe Config. Some features include reading config values as lists, configuration resolution, fallback configs, memory helpers, duration helpers and boolean helpers.
Example Configs
We will be using the following two configuration files defaults.conf
and overrides.conf
.
defaults.conf
conf {
name = "default"
title = "Simple Title"
nested {
whitelistIds = [1, 22, 34]
}
combined = ${conf.name} ${conf.title}
}
featureFlags {
featureA = "yes"
featureB = true
}
overrides.conf
conf {
name = "overrides"
}
redis {
ttl = 5 minutes
}
uploadService {
maxChunkSize = 512k
maxFileSize = 5G
}
Loading a Typesafe Config from a resource
Loading a Typesafe Config from a resource on the classpath is a simple one liner. Typesafe Config has several default configuration locations it looks when loading through ConfigFactory.load();
but we are big fans of making everything explicit. It's preferred if the the configs that were being loaded are listed right there when we call it.
Config defaultConfig = ConfigFactory.parseResources("defaults.conf");
Creating a Typesafe Config with fallbacks
A great use case for this is environment specific overrides. You can set up one defaults file and have an override for local, dev, prod. This can be seen in our environment aware configurations. In this case we first load our overrides.conf
then set a fallback of our defaults.conf
from above. When searching for any property it will first search the original config then traverse down the fallbacks until a value is found. This can be chained many times. We will come back to the resolve()
method a little later.
Config fallbackConfig = ConfigFactory.parseResources("overrides.conf")
.withFallback(defaultConfig)
.resolve();
Loading a simple String config value
Probably one of the most common cases for configs is loading simple strings. Let's see what happens when we request name
and title
from both configs. Each has an entry for name
but only the default.conf
has an entry for title
.
log.info("name: {}", defaultConfig.getString("conf.name"));
log.info("name: {}", fallbackConfig.getString("conf.name"));
log.info("title: {}", defaultConfig.getString("conf.title"));
log.info("title: {}", fallbackConfig.getString("conf.title"));
2017-08-15 09:07:14.461 [main] INFO TypesafeConfigExamples - name: default
2017-08-15 09:07:14.466 [main] INFO TypesafeConfigExamples - name: overrides
2017-08-15 09:07:14.466 [main] INFO TypesafeConfigExamples - title: Simple Title
2017-08-15 09:07:14.466 [main] INFO TypesafeConfigExamples - title: Simple Title
As expected each config returned its own value for name
but for title
the fallback was called since overrides.conf
does not have a config entry.
Config value resolution
Sometimes its useful to reuse a config value inside of other config values. This is achieved through configuration resolution by calling the resolve()
method on a Typesafe Config. An example of this use case could be if all hostnames are scoped per environment ({env}.yourdomain.com) it would be nicer to only override the env
property and have all other values reference it. Let's use the above name
and title
again just for an example. If you look at the example configs we have the following combined = ${conf.name} ${conf.title}
.
log.info("combined: {}", fallbackConfig.getString("conf.combined"));
2017-08-15 09:07:14.466 [main] INFO TypesafeConfigExamples - combined: overrides Simple Title
Duration helpers
How often do you open a config and see something like timeout = 300; // five minutes in seconds 5 * 60
. Typesafe Config is able to parse many durations and allow you to convert it to any other duration using TimeUnit
. Example ttl = 5 minutes
from the above configs.
log.info("redis.ttl minutes: {}", fallbackConfig.getDuration("redis.ttl", TimeUnit.MINUTES));
log.info("redis.ttl seconds: {}", fallbackConfig.getDuration("redis.ttl", TimeUnit.SECONDS));
2017-08-15 09:07:14.472 [main] INFO TypesafeConfigExamples - redis.ttl minutes: 5
2017-08-15 09:07:14.472 [main] INFO TypesafeConfigExamples - redis.ttl seconds: 300
Memory Size Helpers
Just like durations its not uncommon to see confusing values for bytes. Typesafe Config provides us with the getMemorySize
method which can read values like maxChunkSize = 512k
and maxFileSize = 5G
. Don't forget to call toBytes()
.
// Any path in the configuration can be treated as a separate Config object.
Config uploadService = fallbackConfig.getConfig("uploadService");
log.info("maxChunkSize bytes: {}", uploadService.getMemorySize("maxChunkSize").toBytes());
log.info("maxFileSize bytes: {}", uploadService.getMemorySize("maxFileSize").toBytes());
2017-08-15 09:07:14.479 [main] INFO TypesafeConfigExamples - maxChunkSize bytes: 524288
2017-08-15 09:07:14.479 [main] INFO TypesafeConfigExamples - maxFileSize bytes: 5368709120
Handling Lists / Arrays
A quick example of for a list of config options would be a configurable whitelist / blacklist.
List<Integer> whiteList = fallbackConfig.getIntList("conf.nested.whitelistIds");
log.info("whitelist: {}", whiteList);
List<String> whiteListStrings = fallbackConfig.getStringList("conf.nested.whitelistIds");
log.info("whitelist as Strings: {}", whiteListStrings);
2017-08-15 09:07:14.480 [main] INFO TypesafeConfigExamples - whitelist: [1, 22, 34]
2017-08-15 09:07:14.480 [main] INFO TypesafeConfigExamples - whitelist as Strings: [1, 22, 34]
Boolean Helpers
This one is a little questionable, do we really need to allow values such as yes
and no
instead of true
or false
? It's there if you prefer it.
log.info("yes: {}", fallbackConfig.getBoolean("featureFlags.featureA"));
log.info("true: {}", fallbackConfig.getBoolean("featureFlags.featureB"));
2017-08-15 09:07:14.480 [main] INFO TypesafeConfigExamples - yes: true
2017-08-15 09:07:14.480 [main] INFO TypesafeConfigExamples - true: true
Summary
Typesafe Config has many useful features out of the box for free. There are more than the ones simply listed here. A more advanced one could be shown in our Database Connection Pooling in Java with HikariCP example which uses configuration inheritance to create two similair config objects and only override a few properties.