1. Introduction

Configurations are an important part of any software application. Like any other programming language, Scala also has a variety of configuration management libraries. Some of the popular libraries are Lightbend Config, PureConfig, and ClearConfig.

In this article, let’s look at the most popular of them all, Lightbend Config.

2. What Is Lightbend Config?

Config is the most widely used configuration library in Scala. It’s also used by many popular libraries such as Akka, Play, and Slick.

Let’s look at some of the significant features of the Config library:

  • Supports multiple formats(properties, JSON, and HOCON)
  • Supports deeply nested structures
  • Supports variable substitution
  • Can override config values using environment variables and JVM arguments
  • Supports parsing non-primitive types
  • Can merge and include multiple config files

3. Setup

To use Config, we need to add the dependency in build.sbt:

libraryDependencies += "com.typesafe" % "config" % "1.4.2"

4. Load Config

Now, let’s look at how to read config files:

val config: Config = ConfigFactory.load()

This loads the configuration files into the config object. By default, Config looks for files with the file names application.conf and reference.conf from the classpath.

Throughout this tutorial, we’ll be using the HOCON format for the configuration files. However, the same code applies for JSON and property files as well.

5. Read Simple Configuration

Apart from the primitive datatypes, Config can parse types such as List, Finite Duration, and Memory Size. Let’s create a very simple application.conf file:

id = 100
name = "baeldung"
price = 2.0
status = false

We can read the values as primitive types using the methods such as getInt(), getString(), and so on:

val id:Int = config.getInt("id")
val name:String = config.getString("name")
val price: Double = config.getDouble("price")
val status: Boolean = config.getBoolean("status")

If we try to read a configuration that doesn’t exist, the library throws a ConfigException at runtime. To avoid this, we can check if the key exists before accessing it:

val conf = ConfigFactory.load()
intercept[ConfigException](conf.getString("dummy.key"))
assert(conf.hasPath("dummy.key") == false)

Now, let’s try to read more complex data types. Let’s add the following sample configuration to the application.conf:

mem = 1k
delay = 1 second

We can read these non-primitive types using the same config instance:

val delay: time.Duration = config.getDuration("delay") // stores 1 second as Duration
val mem: ConfigMemorySize = config.getMemorySize("mem") // stores 1KB or 1024 bytes as value

6. Reading Nested Configurations

It’s always better to organize the configurations in multiple sub-groups for better readability. So instead of creating a very flat config file, we generally create configurations with nested structures:

app.database {
  postgres {
    url = "localhost:5432",
    username = "user"
  }
}

Now, we can read each property by using the path to that particular field:

val user = config.getString("app.database.postgres.username")
assertEquals(user, "user")

If we have multiple configuration values within a node, we can also read an entire sub-node together and pass it across. For example, we can read the entire postgres node as a single config object and pass it to another method instead of passing each field:

val dbConfig: Config = config.getConfig("app.database")
assertEquals(dbConfig.getString("postgres.username"), "user")
assertEquals(dbConfig.getString("postgres.url"), "localhost:5432")

7. Variable Substitution

We can use a configuration value in another place and Config will substitute the value. To do that, we need to wrap the variable in ${} while using it:

name = "baeldung"<br />sub {
  desc = "This is a "${name}" project"
}

Let’s add the code to read this config:

val desc = config.getString("sub.desc")
assertEquals(desc, "This is a baeldung project")

Here, the value of ${name} is replaced with the value of the configuration field name. We should note that the substitution field’s outside the double quotes in the configuration. Config won’t substitute the field if it’s within the double quotes.

8. Merging Multiple Files

We can merge multiple configuration files together. This stacks the configuration keys across multiple files into a single final file.

By default, the library merges system properties, application.conf, application.json, application.properties, and reference.conf files. It gives priority to the system properties, and then properties from application files and then reference.conf

However, we can’t guarantee which properties will be retained or overwritten if we have multiple configuration files with the same name and properties in the classpath. 

We can also apply the prioritization and merge different config files manually using the method withFallback().

val defaultConf = ConfigFactory.load("application-default.conf")
val conf = ConfigFactory.load("application.conf").withFallback(defaultConf)

Additionally, it’s better to separate different configurations into different files to manage them easily. We can then combine the different configuration files into a single file using the include directive. 

Let’s create two separate config files, one for database and another for HTTP configurations. 

We can add database-related configurations to the file database.conf:

db {
  dbUrl = "postgresql://localhost:5555"
  dbName = "baeldung"
  username = "postgres"
  password = "admin"
}

Similarly, let’s create http.conf file:

http {
  port ="8090"
  host = "localhost"
  protocol = "https"
}

Finally, we can include these 2 files into a single file main.conf with its own configurations as well:

include "http.conf"
include "database.conf"
appName = "Baeldung"

Now, when we load the main.conf file, we can also get the configurations from all the included files:

val conf = ConfigFactory.load("main.conf")
val username = conf.getString("db.username")
val protocol = conf.getString("http.protocol")
assertEquals(username, "postgres")
assertEquals(protocol, "https")

9. Conclusion

In this tutorial, we looked at the Lightbend Config library to read the configuration files.

As always, the sample code used here is available over on GitHub.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.