The Typesafe Config project is easily the best, most convenient, easiest to use, easiest to understand extensible configuration management system I've ever had the pleasure of working with. It has no dependencies, it reads JSON and HOCON (a JSON superset with substitutions, etc.), has excellent merge strategies, accepts command line runtime overrides automatically, and is of course very well tested (coming from Typesafe after all). Ok great, so what does that look like? Here's an example REST API
reference.conf section:rest {
  protocol = http
  host = "0.0.0.0"
  port = 8080
  timeout = 2 seconds
}production.conf that overlays these values:rest {
  protocol = https
  port = 443
  timeout = 5 seconds
}  case class RestService(
    protocol: String, 
    host: String, 
    port: Int, 
    timeout: FiniteDuration
  )
  import scala.concurrent.duration._ // provides implicit millis->FiniteDuration cnvsn
  
  val defaultConf = ConfigFactory.defaultReference()
  val conf = ConfigFactory.load().withFallback(defaultConf)
  
  val secondsFormatter = new PeriodFormatterBuilder()
    .appendSeconds().appendSuffix(" seconds").toFormatter
  val timeoutPeriod = secondsFormatter.parsePeriod(conf.getString("rest.timeout"))  
  
  val restService = RestService(
    conf.getString("rest.protocol"),
    conf.getString("rest.host"),
    conf.getInt("rest.port"),
    timeoutPeriod.toStandardDuration.getMillis.millis
  )
base-api where we have our fancy wrapper:
  import base.common.config.BaseConfig._ // provides HOCON implicit cnvsns 
  val REST = "rest"
  val restService = RestService(
    Keys(REST, "protocol"),
    Keys(REST, "host"),
    Keys(REST, "port"),
    Keys(REST, "timeout")
  )
5 seconds to a strongly typed duration (and it would work equally well if we have put 5 hours or 1 day).The something, somewhere is the
BaseConfig which provides a chain of implicits that will figure out how to populate config values for just about anything HOCON supports, and are easily extensible to custom data types. The primary built-in custom data type is Period, which cycles through a number of formatters attempting to find one that properly parse the provided config value (e.g. "milli", "millis", "millisecond", "milliseconds").
Easy stuff so far. Let's make it interesting.
rest {
  protocol = http
  // ...
  endpoints = [
    { path = foo, methods = [get]       },
    { path = bar, methods = [get, post] }
  ]
}
  object Methods extends Enumeration {
    type Method = Value
    val GET = "get"
    val POST = "post"
    // ...
  }
  case class Endpoint(path: String, methods: Set[Method])
  case class RestService(
    protocol: String, 
    // ... host, port, etc ...
    endpoints: List[Endpoint]
  )
Set[Method] data type let alone a List[Endpoint] data type. But with just a wee bit of extra work we can get these running as smoothly as the simpler types:
  implicit def string2Method(s: String): Method = Methods.withName(s)
  val REST = "rest"
  val restService = RestService(
    Keys(REST, "protocol"),
    getConfigList(Keys(REST, "endpoints")).map { endpointConfig =>
      implicit val config = new BaseConfig(endpointConfig)
      Endpoint(
        Keys("path"), 
        Keys("methods")
      )
    }
  )
endpoint config value is itself a list of Typesafe Configs. This allows us access to the full power of the implicit chain but scoped down to the contents of each index of this value's list of properties. Neat huh?
This is cool, but isn't implicit magic bad?
Some people feel pretty strongly that implicits should be used with extreme caution and that they can quickly run away in a large multi-developer codebase to an indecipherable unmaintainable mess. To that I say: I agree. You have to be really careful with them, and frankly I think using them in core business logic is a mistake that comes back to haunt people frequently - though it's pretty hard to get around their usage in core libs like JSON DSLs. For their usage here, I think it will be OK even in medium to larger size projects, as long as they remain constrained to the configuration system and only deal with common primitives. On the other hand, I wouldn't fault a dev team for saying "no way Jose, not in my codebase" ;)
