{"id":16538321,"url":"https://github.com/japgolly/clear-config","last_synced_at":"2026-01-31T03:00:47.321Z","repository":{"id":34817075,"uuid":"138470056","full_name":"japgolly/clear-config","owner":"japgolly","description":"Scala FP configuration library with a focus on runtime clarity","archived":false,"fork":false,"pushed_at":"2026-01-22T04:24:21.000Z","size":433,"stargazers_count":141,"open_issues_count":10,"forks_count":5,"subscribers_count":5,"default_branch":"master","last_synced_at":"2026-01-22T17:30:52.968Z","etag":null,"topics":["config","configuration","fp","functional-programming","scala","scala-js","scalajs"],"latest_commit_sha":null,"homepage":null,"language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/japgolly.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"patreon":"japgolly"}},"created_at":"2018-06-24T09:41:31.000Z","updated_at":"2026-01-22T04:24:24.000Z","dependencies_parsed_at":"2023-02-16T21:40:15.614Z","dependency_job_id":"69477dad-00ce-417d-9a6e-f717fd056a04","html_url":"https://github.com/japgolly/clear-config","commit_stats":{"total_commits":296,"total_committers":4,"mean_commits":74.0,"dds":"0.057432432432432456","last_synced_commit":"a4f7872531ad8195e669e0dc30dd59a5f019e824"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/japgolly/clear-config","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/japgolly%2Fclear-config","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/japgolly%2Fclear-config/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/japgolly%2Fclear-config/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/japgolly%2Fclear-config/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/japgolly","download_url":"https://codeload.github.com/japgolly/clear-config/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/japgolly%2Fclear-config/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28927762,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-31T02:59:34.861Z","status":"ssl_error","status_checked_at":"2026-01-31T02:59:05.369Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["config","configuration","fp","functional-programming","scala","scala-js","scalajs"],"created_at":"2024-10-11T18:45:01.429Z","updated_at":"2026-01-31T03:00:47.309Z","avatar_url":"https://github.com/japgolly.png","language":"Scala","funding_links":["https://patreon.com/japgolly","https://www.patreon.com/japgolly)!"],"categories":[],"sub_categories":[],"readme":"# ClearConfig\n[![Build Status](https://travis-ci.org/japgolly/clear-config.svg?branch=master)](https://travis-ci.org/japgolly/clear-config) [![Latest version](https://index.scala-lang.org/japgolly/clearconfig/core/latest.svg?color=orange)](https://index.scala-lang.org/japgolly/clearconfig/core)\n\nA type-safe, FP, Scala config library.\n\n```scala\nlibraryDependencies += \"com.github.japgolly.clearconfig\" %%% \"core\" % \"\u003cver\u003e\"\n```\n\n\n# What's special about this?\n\nThere are plenty of config libraries out there, right?\nThis library is pure FP, type-safe, super-simple to use, highly composable and powerful, yada yada yada...\nAll true but it's biggest and most unique feature is actually:\n\n**CLARITY.**\n\nHaven't we all had enough of crap like:\n\n* changing an environment variable setting, pushing all the way though to an environment, testing and\n  then discovering that your expected change didn't occur. Was the new setting picked up?\n  What setting did it use? Where did it come from?\n\n* after hours of frustration: \"That setting isn't even used any more?!\n  Why the hell is it still all over our deployment config?!\n  Why didn't person X magically know to remove this specific piece of text in this big blob of text in this completely\n  separate deployment repo at the same time they made their code change?\"\n\nThis library endeavours to provide **clarity**.\nWhen you get an instance of your config, you also get a report that describes:\n\n* where config comes from\n* how config sources override other sources\n* what values each config source provided\n* what config keys are in use\n* what the total, resulting config is\n* which config is still hanging around but is actually stale and no longer in use\n\n*(sample report below)*\n\n\n# Walkthrough\n\nHere's a pretty common scenario that will serve as a decent introduction.\n\nWe have an app which has the following config:\n\n```scala\nimport java.net.URL\n\nfinal case class DatabaseConfig(\n  port     : Int,\n  url      : URL,\n  username : String,\n  password : String,\n  schema   : Option[String])\n```\n\nLet's define how we populate our config from the outside world...\n\n```scala\nimport japgolly.clearconfig._\nimport cats.implicits._\n\nobject DatabaseConfig {\n\n  def config: ConfigDef[DatabaseConfig] =\n    (\n      ConfigDef.getOrUse(\"PORT\", 8080),\n      ConfigDef.need[URL](\"URL\"),\n      ConfigDef.need[String](\"USERNAME\"),\n      ConfigDef.need[String](\"PASSWORD\"),\n      ConfigDef.get[String](\"SCHEMA\")\n    ).mapN(apply)\n}\n```\n\nGreat, now let's define where we want to read config from.\nAt this point you also need to decide which effect type to use.\nYou'd typically use something like `IO` but for simplicity,\nwe'll just use `Id` and opt-out of safe FP.\n\n```scala\nimport cats.Id\n\ndef configSources: ConfigSources[Id] =\n  ConfigSource.environment[Id] \u003e                                             // Highest priority\n  ConfigSource.propFileOnClasspath[Id](\"/database.props\", optional = true) \u003e //\n  ConfigSource.system[Id]                                                    // Lowest priority\n```\n\nNow we're ready to create a real instance based on the real environment.\n\n```scala\nval dbCfg: DatabaseConfig =\n  DatabaseConfig.config\n    .run(configSources)\n    .getOrDie() // Just throw an exception if necessary config is missing\n```\n\nDone! But so far we're not using the most important feature of the library: the report.\n\nLet's get and print out a report at the end.\n\n1. We'll remove env \u0026 system from unused keys to keep the report small seeing as it's just a demo.\n1. We'll also prefix all keys by `POSTGRES_` to make it look a bit more realistic.\n\n```scala\nval (dbCfg, report) =\n  DatabaseConfig.config\n    .withPrefix(\"POSTGRES_\")\n    .withReport\n    .run(configSources)\n    .getOrDie() // Just throw an exception if necessary config is missing\n\nprintln(report\n\n  // Only show unused env vars and properties that start with POSTGRES\n  // (All unused keys in database.props will remain unfiltered in the report)\n  .mapUnused(_.filterKeys(_.startsWith(\"POSTGRES\"), ConfigSourceName.environment, ConfigSourceName.system))\n\n  // Show the full report\n  .full\n)\n```\n\nSample output:\n\n```text\n4 sources (highest to lowest priority):\n  - Env\n  - cp:/database.properties\n  - System\n  - Default\n\nUsed keys (5):\n+-------------------+------+-------------------------+---------+\n| Key               | Env  | cp:/database.properties | Default |\n+-------------------+------+-------------------------+---------+\n| POSTGRES_PASSWORD |      | Obfuscated (1C02B9F6)   |         |\n| POSTGRES_PORT     | 4000 |                         | 8080    |\n| POSTGRES_SCHEMA   |      |                         |         |\n| POSTGRES_URL      |      | http://localhost/blah   |         |\n| POSTGRES_USERNAME |      | demo                    |         |\n+-------------------+------+-------------------------+---------+\n\nUnused keys (1):\n+----------------+-------------------------+\n| Key            | cp:/database.properties |\n+----------------+-------------------------+\n| POSTGRES_SCHMA | public                  |\n+----------------+-------------------------+\n```\n\nFrom the above report we can immediately observe the following:\n\n* Which sources override other sources; the report columns (left-to-right) respect this\n* We'll be running at port 4000 and the reason for that is there's an override set by the environment\n* There's a typo in our `database.properties`; `POSTGRES_SCHMA` should be `POSTGRES_SCHEMA`\n* The password value has been hashed for the report. This still allows you to compare the hash between envs or time to determine change without compromising the value.\n\n\n# Usage\n\n* Simplest and most common methods:\n\n  ```scala\n  ConfigDef.get     [A](key: String)             // provides an Option[A] - optional config\n  ConfigDef.getOrUse[A](key: String, default: A) // provides an A - optional config with default\n  ConfigDef.need    [A](key: String)             // provides an A - mandatory config; error if not provided\n  ```\n\n* To define your own type of config value, create an implicit `ConfigValueParser`. Example:\n\n  ```scala\n  sealed trait MyBool\n  case object Yes extends MyBool\n  case object No extends MyBool\n\n  implicit val myBoolParser: ConfigValueParser[MyBool] =\n    ConfigValueParser.oneOf[MyBool](\"yes\" -\u003e Yes, \"no\" -\u003e No)\n      .preprocessValue(_.toLowerCase) // Make it case-insensitive\n  ```\n\n* Call `.secret` on your `ConfigDef` to force it to be obfuscated in the report.\n  The default (implicit) report settings already obfuscate keys that contain substrings like\n  `password`, `credential`, and `secret`. Override `configReportSettings` if required.\n\n* Shell-style Comments (beginning with `#`) are automatically removed from config values.\n  Create your own implicit `ConfigValuePreprocessor` to customise this behaviour.\n\n* Keys can be modified after the fact. Eg. `ConfigDef.get(\"A\").withPrefix(\"P_\").withKeyMod(_.replace('_', '.'))`\n  is equivalent to `ConfigDef.get(\"P.A\")`. This also works when a `ConfigDef` is a composition of more than\n  one key, in which case they'll all be modified.\n\n* There is special DSL to create `A =\u003e Unit` functions to configure a mutable object (which you typically use when working with a Java library)\n  `ConfigDef.consumerFn[A](...)`. There is an example below:\n\n* `ConfigDef.logbackXmlOnClasspath` can be used to parse logback xml for its use of environment variables\n\n* More... (explore the source)\n\n# Larger Example\n\nYou typically compose using `Applicative`, give the composite a prefix,\nthen use (nest) it in some higher-level config.\n\nFor example, this Scala code...\n\n```scala\nimport cats.syntax.apply._\nimport japgolly.clearconfig._\nimport java.net.{URI, URL}\nimport redis.clients.jedis.JedisPoolConfig\n\ncase class AppConfig(postgres: PostgresConfig, redis: RedisConfig, logLevel: LogLevel)\n\nobject AppConfig {\n  def config: ConfigDef[AppConfig] =\n    ( PostgresConfig.config,\n      RedisConfig.config,\n      ConfigDef.getOrUse(\"log_level\", LogLevel.Info)\n    ).mapN(apply)\n      .withPrefix(\"myapp.\")\n}\n\ncase class PostgresConfig(url: URL, credential: Credential, schema: Option[String])\n\nobject PostgresConfig {\n  def config: ConfigDef[PostgresConfig] =\n    ( ConfigDef.need[URL](\"url\"),\n      Credential.config,\n      ConfigDef.get[String](\"schema\"),\n    ).mapN(apply)\n      .withPrefix(\"postgres.\")\n}\n\ncase class Credential(username: String, password: String)\n\nobject Credential {\n  def config: ConfigDef[Credential] =\n    ( ConfigDef.need[String](\"username\"),\n      ConfigDef.need[String](\"password\"),\n    ).mapN(apply)\n}\n\ncase class RedisConfig(uri: URI, credential: Credential, configurePool: JedisPoolConfig =\u003e Unit)\n\nobject RedisConfig {\n\n  def poolConfig: ConfigDef[JedisPoolConfig =\u003e Unit] =\n    ConfigDef.consumerFn[JedisPoolConfig](\n      _.get(\"block_when_exhausted\", _.setBlockWhenExhausted),\n      _.get(\"eviction_policy_class_name\", _.setEvictionPolicyClassName),\n      _.getOrUse(\"fairness\", _.setFairness)(true),\n      _.get(\"jmx_enabled\", _.setJmxEnabled),\n      _.get(\"jmx_name_base\", _.setJmxNameBase),\n      _.get(\"jmx_name_prefix\", _.setJmxNamePrefix),\n      _.get(\"lifo\", _.setLifo),\n      _.get(\"max_idle\", _.setMaxIdle),\n      _.get(\"max_total\", _.setMaxTotal),\n      _.get(\"max_wait_millis\", _.setMaxWaitMillis),\n      _.get(\"min_evictable_idle_time_millis\", _.setMinEvictableIdleTimeMillis),\n      _.getOrUse(\"min_idle\", _.setMinIdle)(2),\n      _.get(\"num_tests_per_eviction_run\", _.setNumTestsPerEvictionRun),\n      _.get(\"soft_min_evictable_idle_time_millis\", _.setSoftMinEvictableIdleTimeMillis),\n      _.get(\"test_on_borrow\", _.setTestOnBorrow),\n      _.get(\"test_on_create\", _.setTestOnCreate),\n      _.get(\"test_on_return\", _.setTestOnReturn),\n      _.get(\"test_while_idle\", _.setTestWhileIdle),\n      _.get(\"time_between_eviction_runs_millis\", _.setTimeBetweenEvictionRunsMillis)\n    )\n\n  def config: ConfigDef[RedisConfig] =\n    ( ConfigDef.need[URI](\"uri\"),\n      Credential.config,\n      poolConfig.withPrefix(\"pool.\"),\n    ).mapN(apply)\n      .withPrefix(\"redis.\")\n}\n\nsealed trait LogLevel\nobject LogLevel {\n  case object Debug extends LogLevel\n  case object Info extends LogLevel\n  case object Warn extends LogLevel\n\n  implicit def configValueParser: ConfigValueParser[LogLevel] =\n    ConfigValueParser.oneOf[LogLevel](\"debug\" -\u003e Debug, \"info\" -\u003e Info, \"warn\" -\u003e Warn)\n      .preprocessValue(_.toLowerCase)\n}\n```\n\nwill read the following properties:\n\n```\nmyapp.postgres.password\nmyapp.postgres.schema\nmyapp.postgres.url\nmyapp.postgres.username\n\nmyapp.redis.password\nmyapp.redis.uri\nmyapp.redis.username\n\nmyapp.redis.pool.block_when_exhausted\nmyapp.redis.pool.eviction_policy_class_name\nmyapp.redis.pool.fairness\nmyapp.redis.pool.jmx_enabled\nmyapp.redis.pool.jmx_name_base\nmyapp.redis.pool.jmx_name_prefix\nmyapp.redis.pool.lifo\nmyapp.redis.pool.max_idle\nmyapp.redis.pool.max_total\nmyapp.redis.pool.max_wait_millis\nmyapp.redis.pool.min_evictable_idle_time_millis\nmyapp.redis.pool.min_idle\nmyapp.redis.pool.num_tests_per_eviction_run\nmyapp.redis.pool.soft_min_evictable_idle_time_millis\nmyapp.redis.pool.test_on_borrow\nmyapp.redis.pool.test_on_create\nmyapp.redis.pool.test_on_return\nmyapp.redis.pool.test_while_idle\nmyapp.redis.pool.time_between_eviction_runs_millis\n\nmyapp.log_level\n```\n\n\n##### Support:\nIf you like what I do\n—my OSS libraries, my contributions to other OSS libs, [my programming blog](https://japgolly.blogspot.com)—\nand you'd like to support me, more content, more lib maintenance, [please become a patron](https://www.patreon.com/japgolly)!\nI do all my OSS work unpaid so showing your support will make a big difference.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjapgolly%2Fclear-config","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjapgolly%2Fclear-config","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjapgolly%2Fclear-config/lists"}