Typesafe Config & HOCON in examples

Few months ago I’ve started my journey with Play Framework and Akka. I loved their configuration syntax, the HOCON – Human-Optimized Config Object Notation.

If you don’t know what I’am talking writing about, you have to follow these links below to see what the word “awesome” means 😉

http://www.playframework.com/documentation/2.2.x/Configuration

https://github.com/typesafehub/config/blob/master/HOCON.md

I want to show you how easily HOCON can be adopted into your (JVM-based) projects.

Set up your project

This step will be indecently straightforward since everything you need to do is just ensure that appropriate JAR is present in the application’s class path. No matter what programming language are you using – Java, Scala, Groovy, yet another or maybe all of them.

If you are using Maven or SBT just add following dependency:


<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
<version>1.0.2</version>
</dependency>

view raw

maven_dep.xml

hosted with ❤ by GitHub


"com.typesafe" % "config" % "1.0.2"

view raw

sbt_dep.sbt

hosted with ❤ by GitHub

Before We start..

Say hello to com.typesafe.config.Config interface which provides API for obtaining values (or even whole nested documents as a Config instance) for given path.

You can find more (up-to-date) informations in the Config’s JavaDoc.

You will be working almost only with this single interface, so I it’s recommended to read this JavaDoc. It is short and you will get comprehension about differences between Config vs ConfigObject, Keys vs Paths and immutability aspects.

It’s absolutely must-read if you want to know not only how to use library but also how it works and how do they did it.

Let’s create our first Config instance!

You can get Config instance using the com.typesafe.config.ConfigFactory, a dedicated factory.

The simplest way to start with Typesafe’s Config is to place application.conf containing JSON/HOCON configurations and load them using:

ConfigFactory.load()

You can find a lot of other methods in the ConfigFactory’s documentation.

Don’t omit description of the ConfigFactory.load() method – it provides useful informations about customizing config location on deployment environment (in a nutshell – using ConfigFactory.load() invocation you can set appropriate property and let Config to load contents from given URL or a file with specified path) or how to get a fresh configuration (instead of the cached one).

Examples source code

All of examples will be presented in Scala (because of conicise syntax).
If you aren’t familiar with Scala you can find these examples written in Java at my github repository -> https://github.com/mkubala/typesafe-config-examples.
I encourage you to cloning/forking my repo, modifying the code and having fun 😉

Basic usages

All of my examples uses the same HOCON file as a configuration source:


my.organization {
project {
name = "DeathStar"
description = ${my.organization.project.name} "is a tool to take control over whole world. By world I mean couch, computer and fridge ;)"
}
team {
members = [
"Aneta"
"Kamil"
"Lukasz"
"Marcin"
]
}
}
my.organization.team.avgAge = 26

First, Let’s try to read some values:


val config = ConfigFactory.load()
config.getString("my.organization.project.name") // => DeathStar
config.getString("my.organization.project.description") // => DeathStar is a tool to take control over whole world. By world I mean couch, computer and fridge 😉
config.getInt("my.organization.team.avgAge") // => 26
config.getStringList("my.organization.team.members") // => [Aneta, Kamil, Lukasz, Marcin]

Pretty simple, isn’t it?

As you may already noticed my cofiguration has tree-like structure.
You can easily extract only a particular bunch of branches (node with its children) to the new Config object:


val config = ConfigFactory.load();
val extractedConfig: Config = config.getConfig("my.organization.team");
extractedConfig.getInt("avgAge") // => 26
extractedConfig.getStringList("members") // => [Aneta, Kamil, Lukasz, Marcin]

When it may be useful? Consider following use-case:
We want to allow further application’s administrator to define some set of predefined users.
We’ve defined UserObject which will be Config consumer:


public class UserObject {
private final String name;
private final int age;
public UserObject(final Config params) {
name = params.getString("name");
age = params.getInt("age");
}
// getters, hashCode, equals, etc.
}

view raw

UserObject.java

hosted with ❤ by GitHub

We also provide some sample configuration:


some {
namespace {
users = [
{
name = "firstUser"
age = 25
}
{
name = "secondUser"
age = 35
}
]
}
}

The rest is pretty simple:

  1. extract a list of nested user Configs (using .getConfigList(“path”))
  2. pass them with the UserObject’s constructor, transforming (mapping) list from step #1 into a collection of UserObjects


import scala.collection.JavaConversions._
val mainConfig = ConfigFactory.load("nestedConfExample")
mainConfig.getConfigList("some.namespace.users") map (new UserObject(_))
// => ArrayBuffer(UserObject{name='firstUser', age=25}, UserObject{name='secondUser', age=35})

Fallbacks

You can also compose many Config instances into a fallback chain:


val customs: Config = ConfigFactory.load("custom").withFallback(config)
customsval customs: Config = ConfigFactory.load("custom").withFallback(config).getString("my.organization.project.name") // => <Name overriden in custom.conf>
customs.getString("my.organization.team.avgAge") // => 26

It will be useful when you want to provide some default values which will be open for overriding and closed for modification.

Iterating over paths and values

Here is how I kill two birds with one stone – I get a rid of all unnecessary values outside a specific path (“my.organization”), iterate throught each of Config’s entries and extract the keys.


config.withOnlyPath("my.organization").entrySet() map (_.getKey)
// => Set(my.organization.team.members, my.organization.project.name,
// my.organization.team.avgAge, my.organization.project.description)

Method .withOnlyPath(..) used at the above example will clone the config, retaining only the given path (and its children) – all sibling paths will be removed.
Use Config.atPath(..) If you want to preserve siblings.
There’s also similar method – Config.atPath(..) which places the config inside another Config at the given path, preserving siblings:


config.atPath("my.organization").entrySet() map (_.getKey)
// => Set(my.organization.java.library.path, my.organization.java.runtime.name,
// my.organization.sun.cpu.isalist, my.organization.awt.toolkit, my.organization.java.specification.version,
// my.organization.my.organization.project.description, my.organization.java.vm.specification.name,
// my.organization.http.nonProxyHosts, my.organization.java.version, my.organization.sun.boot.class.path,
/// and so on..

Values overriding

The section’s title doesn’t need explanation, so let’s look at the examples:


config.getString("my.organization.project.name") // => DeathStar
// Let's override project's name with String "RebrandedProject"
val updatedConfig = config.withValue("my.organization.project.name", ConfigValueFactory.fromAnyRef("RebrandedProject")
updatedConfig.getString("my.organization.project.name") // => RebrandedProject
updatedConfig.getString("my.organization.project.description") // => DeathStar is a tool to take control over whole world. By world I mean couch, computer and fridge 😉
// Because of immutability the original Config stay untouched
config.getString("my.organization.project.name") // => DeathStar

Hey, what happened at line #5 – substitution expresions weren’t re-evaluated after replacing value! To be honest – I have no idea how to make it working.. Yet 😉

It’s worth to mention that you can override values using env variables.
Try to run examples with -Dmy.organization.project.description=”overriden by env” and see what happens 😉

You can find more examples at the Config’s repository, right here.

HOCON generating

Now it’s time for the missing part of the official Config’s examples – document generation and rendering.

Creating Configs

We have seen before how to load/create new Config objects using ConfigFactory.load, Config.atPath, Config.withOnlyPath, Config.getConfig and Config.getConfigList.

But how to create a Config from scratch?
You can use ConfigFactory.empty() factory method which returns an empty Config.
When you’ll be going to fill them using Config.withValue(..), you have to keep in mind that Config is immutable! This means that each invocation of withValue(..) produces a new Config instead of modifying object on which you invoke it.

I’m a bit hungry so the next example will be course-related. It represents pseudo-recipe for one of my favourite dish:


import scala.collection.JavaConversions._
ConfigFactory.empty()
.withValue("dish.name", ConfigValueFactory.fromAnyRef("Zapiekanka"))
.withValue("dish.estimatedCost", ConfigValueFactory.fromAnyRef(10))
.withValue("dish.ingredients", ConfigValueFactory.fromIterable(
List(
"potato", "bacon", "onion", "salt", "pepper"
)))

Rendering Config as a HOCON

Ok, let’s try to produce a valid HOCON document (as a String) representing previously created recipe:


// See ConfigRenderOptions' JavaDoc for details.
val renderOpts = ConfigRenderOptions.defaults().setOriginComments(false).setComments(false).setJson(false);
val dishConfig =// as in creating Conf example
dishConfig.root().render(renderOpts)
// => dish {
// ingredients=[
// potato,
// bacon,
// onion,
// salt,
// pepper
// ]
// name=SomeCompany
// estimatedCost=10
// }

Note: If you omit the .setJson(false) invocation, you will get a JSON document as an output, instead of HOCON.

Conclusion

Typesafe Config is a simple and powerful tool. It supports not only the HOCON but also Java properties and JSON as an input.

There is still few useful features not covered in this blog post.
You can find them, well described, at the official documentation (for example config validation).

Please feel free to share your reflections about library or this note.
English isn’t my native language therefore I’ll be grateful for any feedback about my grammar and/or vocabulary.

Advertisement

9 thoughts on “Typesafe Config & HOCON in examples

    • Documents generation & rendering is in my opinion one of the biggest missing part in official samples.
      I’ll try to change this via pull requests as soon as I find enough spare time to prepare some additional samples.

      PS: Good curd is never a bad option 😉

  1. Pingback: Pretty printing Akka config | Adventures of a space monkey
  2. There is something special about ConfigFactory.load(). It definitely merges all the reference.conf files from other projects as well as your reference.conf. Then I think it calls .resolve(). That means in values overriding #5 you’d have to put your override into the load() method. Like this:
    ConfigFactory.load(“custom-overrides”). If you do fallback chaining which have references in them you seem to have to call .resolve() on them like this:
    ConfigFactory.load().withFallback(configWithOverrides).resolve()

    Am I right? I am just learning this stuff myself. I’m using onejar/shadowjar and it’s been a bit tricky getting other reference confs in with my own. I’ve been trying to roll my own reference-app.conf and when you add your own config with a property that overrides some 3rd party value (eg. like akka.loglevel=”INFO”) you run into this issue.
    http://doc.akka.io/docs/akka/snapshot/general/configuration.html#When_using_JarJar__OneJar__Assembly_or_any_jar-bundler

  3. @MarcinKubala

    Hello! I just wanted to say, this blog post has been super useful 🙂 Just wanted to point out that I think there’s a small but significant error. I believe the nestedConfExample.conf is missing a “,” between the elements in your array.
    Can you clarify?
    Thanks,
    Simon

  4. Pingback: SCALA : CONFIG | Sacha's Blog Of Programmaticalness

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s