Loading a Config

In Quick Start we used ConfigSource.default.load[MyClass] to read a config from an application.conf resource and convert it to a case class. ConfigSource.default is an instance of ConfigSource - a trait representing sources from which we can load configuration data. This ConfigSource in particular reads and builds a config according to Typesafe Config’s standard behavior, which means it can be used as a replacement for ConfigFactory.load in codebases already using Typesafe Config.

The ConfigSource companion object defines many other ready-to-use sources, like:

  • ConfigSource.file - reads a config from a file in a file system;
  • ConfigSource.resources - reads a config from resources in your classpath or packaged application;
  • ConfigSource.url - reads a config from a URL;
  • ConfigSource.string - reads a literal config from a string.

After you have a config source you can load your config using several methods:

import pureconfig._
import pureconfig.generic.auto._

case class Conf(name: String, age: Int)

val source = ConfigSource.string("{ name = John, age = 33 }")
// reads a config and loads it into a `Either[ConfigReaderFailures, Conf]`
source.load[Conf]
// res0: ConfigReader.Result[Conf] = Right(Conf("John", 33))

// reads a config and loads it into a `Conf` (throwing if not possible)
source.loadOrThrow[Conf]
// res1: Conf = Conf("John", 33)

// reads a raw config from `source`
source.config()
// res2: ConfigReader.Result[com.typesafe.config.Config] = Right(
//   Config(SimpleConfigObject({"age":33,"name":"John"}))
// )

// reads a config as a `ConfigCursor` (see "Config Cursors" section)
source.cursor()
// res3: ConfigReader.Result[ConfigCursor] = Right(
//   ConfigObjectCursor(SimpleConfigObject({"age":33,"name":"John"}), List())
// )

As you use PureConfig you’ll find yourself using mostly load and loadOrThrow. The last two examples would be used only in more complex or specific scenarios.

Combining Sources

A common pattern when loading configs is to read and merge from multiple config sources - maybe you have app-specific and user-specific configs you want to merge in some order, or maybe you want to fall back to some default configuration if a file doesn’t exist or cannot be read.

Most ConfigSource instances are also instances of ConfigObjectSource - a more specific type of source that is guaranteed to produce a config object (instead of say, an array or a scalar value). ConfigObjectSource instances are equipped with an .withFallback method you can use to merge configs:

val appSource = ConfigSource.string("{ age = 33 }")
val defaultsSource = ConfigSource.string("{ name = Admin, age = -1 }")
appSource.withFallback(defaultsSource).load[Conf]
// res4: ConfigReader.Result[Conf] = Right(Conf("Admin", 33))

Sometimes you want some of the sources in your chain to be optional. You can call .optional on any ConfigObjectSource to make it produce an empty config if the underlying source cannot be read:

val otherAppSource = ConfigSource.file("non-existing-file.conf")
otherAppSource.withFallback(defaultsSource).load[Conf]
// res5: ConfigReader.Result[Conf] = Left(
//   ConfigReaderFailures(
//     CannotReadFile(
//       non-existing-file.conf,
//       Some(
//         java.io.FileNotFoundException: non-existing-file.conf (No such file or directory)
//       )
//     ),
//     WrappedArray()
//   )
// )

otherAppSource.optional.withFallback(defaultsSource).load[Conf]
// res6: ConfigReader.Result[Conf] = Right(Conf("Admin", -1))

You also have the option to use an alternative source in case your primary source can’t be read by using .recoverWith:

otherAppSource.recoverWith { case _ => defaultsSource }.load[Conf]
// res7: ConfigReader.Result[Conf] = Right(Conf("Admin", -1))

Loading a Config in a Path

You may want your application config to be loaded from a specific path in the config files, e.g. if you want to have configs for multiple apps in the same sources. ConfigSource instances have an .at method you can use to specify where you want the config to be read from:

val multiAppSource = ConfigSource.string("""
    app-a: {
        timeout: 5s
        retries: 3
    }
    app-b: {
        host: example.com
        port: 8087
    }
""")

case class MyAppConf(host: String, port: Int)
multiAppSource.at("app-b").load[MyAppConf]
// res8: ConfigReader.Result[MyAppConf] = Right(MyAppConf("example.com", 8087))

Customizing Typesafe Config’s Behavior

If you want only parts of Typesafe Config’s standard behavior or want to customize something in their pipeline, PureConfig provides ConfigSources like defaultReference, defaultApplication, defaultOverrides and systemProperties, which you can use to mix and match to fit your needs. Take a look at their Scaladoc for more information about each of them.