{"id":13626753,"url":"https://github.com/gestalt-config/gestalt","last_synced_at":"2025-04-06T20:13:32.734Z","repository":{"id":39640195,"uuid":"324226039","full_name":"gestalt-config/gestalt","owner":"gestalt-config","description":"A Java configuration library","archived":false,"fork":false,"pushed_at":"2024-10-30T01:25:52.000Z","size":4037,"stargazers_count":80,"open_issues_count":16,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-30T03:57:15.230Z","etag":null,"topics":["configuration","gradle","java","kotlin","kotlin-library"],"latest_commit_sha":null,"homepage":"https://gestalt-config.github.io/gestalt/","language":"Java","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/gestalt-config.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2020-12-24T19:47:17.000Z","updated_at":"2024-10-24T08:22:37.000Z","dependencies_parsed_at":"2023-02-14T14:46:30.456Z","dependency_job_id":"504eb83c-553b-4325-9ae4-abd8fe315981","html_url":"https://github.com/gestalt-config/gestalt","commit_stats":{"total_commits":458,"total_committers":3,"mean_commits":"152.66666666666666","dds":"0.24017467248908297","last_synced_commit":"eb1c61d7a2db03082199584ecd5cfc0cc455d540"},"previous_names":[],"tags_count":82,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gestalt-config%2Fgestalt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gestalt-config%2Fgestalt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gestalt-config%2Fgestalt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gestalt-config%2Fgestalt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gestalt-config","download_url":"https://codeload.github.com/gestalt-config/gestalt/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247543595,"owners_count":20955865,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["configuration","gradle","java","kotlin","kotlin-library"],"created_at":"2024-08-01T22:00:17.216Z","updated_at":"2025-04-06T20:13:32.722Z","avatar_url":"https://github.com/gestalt-config.png","language":"Java","funding_links":[],"categories":["项目","Projects"],"sub_categories":["配置","Configuration"],"readme":"# Gestalt\n[![Maven Central](https://img.shields.io/maven-central/v/com.github.gestalt-config/gestalt-core?label=MavenCentral\u0026logo=apache-maven)](https://search.maven.org/artifact/com.github.gestalt-config/gestalt-core)\n[![License](https://img.shields.io/github/license/gestalt-config/gestalt.svg)](LICENSE)\n[![codecov](https://codecov.io/gh/gestalt-config/gestalt/branch/main/graph/badge.svg)](https://codecov.io/gh/gestalt-config/gestalt)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=gestalt-config_gestalt\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=gestalt-config_gestalt)\n\n### **Visit the official documentation: https://gestalt-config.github.io/gestalt/**\n\nAll documentation is being migrated to the official documentation site. \n\nGestalt is a powerful Java configuration library designed to simplify the way you handle and manage configurations in your software projects. In the rapidly evolving landscape of software development, having a flexible and reliable configuration management system is essential for ensuring the smooth operation of your applications.\n\nGestalt offers a comprehensive solution to the challenges of configuration management. It allows you to source configuration data from multiple inputs, merge them intelligently, and present them in a structured, type-safe manner. Whether you're working with Java beans, lists, sets, or primitive data types, Gestalt's automatic decoding based on data types simplifies the process.\n\nThis documentation will guide you through the key features of Gestalt, demonstrate how to get started quickly, and provide detailed insights into its capabilities. Whether you're a seasoned Java developer or just beginning your journey, Gestalt will empower you to manage your application configurations effectively and efficiently.\n\nLet's dive in and explore how Gestalt can streamline your configuration management workflow and help you build more robust and adaptable software.\n \n# Features\n- **Automatic decoding based on type:** Supports decoding into bean classes, lists, sets, or primitive types. This simplifies configuration retrieval.\n\n- **Java Records:** Full support for Java Records, constructing records from configuration using the Records Canonical Constructor.\n\n- **Supports Multiple Formats:** Load configurations from various sources, including Environment Variables, Property files, an in-memory map, and more.\n\n- **Read Sub-sections of Your Config:** Easily navigate to specific sub-sections within configurations using dot notation.\n\n- **Kotlin interface:** Full support for Kotlin with an easy-to-use Kotlin-esque interface, ideal for Kotlin projects.\n\n- **Merge Multiple Sources:** Seamlessly merge configurations from different sources to create comprehensive settings.\n\n- **String Substitution:** Build a config value by injecting Environment Variables, System Properties or other nodes into your strings. Evaluate the substitution at either load time or run time. \n\n- **node Substitution:** Include whole config nodes loaded from files or other places in the config tree anywhere in your config tree.\n\n- **A/B Testing** Segregate results based on groups or random results. See A/B Testing in the use cases section.\n\n- **Flexible and Configurable:** The library offers well-defined interfaces, allowing customization and extension.\n\n- **Easy-to-Use Builder:** Get started quickly with a user-friendly builder, or customize specific aspects of the library.\n\n- **Receive All Errors Up Front:** In case of configuration errors, receive multiple errors in a user-friendly log for efficient debugging.\n\n- **Modular Support for Features:** Include only the required features and dependencies in your build, keeping your application lightweight.\n\n- **Zero Dependencies:** The core library has zero external dependencies; add features and dependencies as needed.\n\n- **Java 11 Minimum:** Requires a minimum of Java 11 for compatibility with modern Java versions.\n\n- **Java Modules:** Supports Java 9 modules with proper exports.\n\n- **Well Tested:** Our codebase boasts an impressive ~92% code coverage, validated by over 1975 meaningful tests.\n\n\n\n# Getting Started\n1. Add the Bintray repository:\n\nVersions 0.1.0 through version 0.11.0 require Java 8. Versions 0.12.0 plus require Java 11.\n\n```kotlin\nrepositories {\n    mavenCentral()\n}\n```\n2. Import gestalt-core, and the specific modules you need to support your use cases.\n\n   Gradle example:\n```groovy\nimplementation 'com.github.gestalt-config:gestalt-core:${version}'\nimplementation 'com.github.gestalt-config:gestalt-kotlin:${version}'\n```\nOr with the kotlin DSL:\n```kotlin\nimplementation(\"com.github.gestalt-config:gestalt-core:$version\")\nimplementation(\"com.github.gestalt-config:gestalt-kotlin:$version\")\n```\nMaven Example:\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.gestalt-config\u003c/groupId\u003e\n  \u003cartifactId\u003egestalt-core\u003c/artifactId\u003e\n  \u003cversion\u003e${version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n3. Setup your configuration files\n\nMultiple types of configurations are supported from multiple sources.\nHere is an example of the `default.properties`:\n```properties\ndb.hosts[0].user=credmond\ndb.hosts[0].url=jdbc:postgresql://localhost:5432/mydb1\ndb.hosts[1].user=credmond\ndb.hosts[1].url=jdbc:postgresql://localhost:5432/mydb2\ndb.hosts[2].user=credmond\ndb.hosts[2].url=jdbc:postgresql://localhost:5432/mydb3\ndb.connectionTimeout=6000\ndb.idleTimeout=600\ndb.maxLifetime=60000.0\n\nhttp.pool.maxTotal=100\nhttp.pool.maxPerRoute=10\nhttp.pool.validateAfterInactivity=6000\nhttp.pool.keepAliveTimeoutMs=60000\nhttp.pool.idleTimeoutSec=25\n```\nHere is an example of the `dev.properties`:\n```properties\ndb.hosts[0].url=jdbc:postgresql://dev.host.name1:5432/mydb\ndb.hosts[1].url=jdbc:postgresql://dev.host.name2:5432/mydb\ndb.hosts[2].url=jdbc:postgresql://dev.host.name3:5432/mydb\ndb.connectionTimeout=600\n\nhttp.pool.maxTotal=1000\nhttp.pool.maxPerRoute=50\n```\n\n4. Construct Gestalt using the builder.\n\n   Use the builder to construct the Gestalt library. It is possible to do this manually, but the builder greatly simplifies the construction of the library. It uses the service loader to automatically load all the default dependencies.\n```java\n  // Create a map of configurations we wish to inject. \n  Map\u003cString, String\u003e configs = new HashMap\u003c\u003e();\n  configs.put(\"db.hosts[0].password\", \"1234\");\n  configs.put(\"db.hosts[1].password\", \"5678\");\n  configs.put(\"db.hosts[2].password\", \"9012\");\n  configs.put(\"db.idleTimeout\", \"123\");\n\n  // Load the default property files from resources.\n  URL devFileURL = GestaltSample.class.getClassLoader().getResource(\"dev.properties\");\n  File devFile = new File(devFileURL.getFile());\n\n  // using the builder to layer on the configuration files.\n  // The later ones layer on and over write any values in the previous\n  Gestalt gestalt = new GestaltBuilder()\n    .addSource(ClassPathConfigSourceBuilder.builder().setResource(\"/default.properties\").build())  // Load the default property files from resources. \n    .addSource(FileConfigSourceBuilder.builder().setFile(devFile).build())\n    .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())\n    .build();\n\n  // Loads and parses the configurations, this will throw exceptions if there are any errors. \n  gestalt.loadConfigs();\n```\n\n5. Retrieve configurations from Gestalt\n\n   Using the Gestalt Interface you can load sub nodes with dot notation into a wide variety of classes.\n   For non-generic classes you can pass in the class with `getConfig(\"db.port\", Integer.class)` or for classes with generic types we need to use a special TypeCapture wrapper that captures the generic type at runtime. This allows us to construct generic classes with such as List\u003cString\u003e using  `new TypeCapture\u003cList\u003cString\u003e\u003e() {}`\n\n```java\nShort myShortWrapper = gestalt.getConfig(\"http.pool.maxTotal\", Short.class);\nHttpPool pool = gestalt.getConfig(\"http.pool\", HttpPool.class);\nList\u003cHttpPool\u003e httpPoolList = gestalt.getConfig(\"http.pools\", new TypeCapture\u003c\u003e() { });\nvar httpPoolList = gestalt.getConfig(\"http.pools\", new TypeCapture\u003cList\u003cHttpPool\u003e\u003e() { });\n```\n\nThe API to retrieve configurations:\n```java\n  /**\n   * Get a config for a path and a given class. \n   * If the config is missing or there are any errors it will throw a GestaltException\n   */\n  \u003cT\u003e T getConfig(String path, Class\u003cT\u003e klass) throws GestaltException;\n\n  /**\n   * Get a config for a path and a given class.\n   * If the config is missing, invalid or there was an exception it will return the default value.\n   */\n  \u003cT\u003e T getConfig(String path, T defaultVal, Class\u003cT\u003e klass);\n\n  /**\n   * Get a config Optional for a path and a given class. \n   * If the config is missing, invalid or there was an exception it will return an Optional.empty()\n   */\n  \u003cT\u003e Optional\u003cT\u003e getConfigOptional(String path, Class\u003cT\u003e klass);\n```   \n\n\n# Config Sources\nAdding a ConfigSource to the builder is the minimum step needed to build the Gestalt Library.\nYou can add several ConfigSources to the builder and Gestalt, and they will be loaded in the order they are added. Where each new source will be merged with the existing source and where applicable overwrite the values of the previous sources. Each Config Source can be a diffrent format such as json, properties or Snake Case Env Vars, then internally they are converted into a common config tree. \n\n```java\n  Gestalt gestalt = builder\n    .addSource(FileConfigSourceBuilder.builder().setFile(defaults).build())\n    .addSource(FileConfigSourceBuilder.builder().setFile(devFile).build())\n    .addSource(EnvironmentConfigSourceBuilder.builder().setPrefix(\"MY_APP_CONFIG\").build())\n    .build();\n```\nIn the above example we first load a file defaults, then load a file devFile and overwrite any defaults, then overwrite any values from the Environment Variables.\nThe priority will be Env Vars \u003e devFile \u003e defaults.\n\n# Config Tree\nThe config files are loaded and merged into a config tree. While loading into the config tree all node names and paths are converted to lower case and for environment variables we convert screaming snake case into dot notation. However, we do not convert other cases such as camel case into dot notation. So if your configs use a mix of dot notation and camel case, the nodes will not be merged. You can configure this conversion by providing your own `Sentence Lexer` in the `GestaltBuilder`. The config tree has a structure (sort of like json) where the root has one or more nodes or leafs. A node can have one or more nodes or leafs. A leaf can have a value but no further nodes or leafs. As we traverse the tree each node or leaf has a name and in combination it is called a path. A path can not have two leafs or both a node and a leaf at the same place. If this is detected Gestalt will throw an exception on loading with details on the path.    \n\nValid:\n```properties\n\ndb.connectionTimeout=6000\ndb.idleTimeout=600\ndb.maxLifetime=60000.0\n\nhttp.pool.maxTotal=1000\nhttp.pool.maxPerRoute=50\n```\n\nInvalid:\n```properties\n\ndb.connectionTimeout=6000\ndb.idleTimeout=600\ndb=userTable                #invalid the path db is both a node and a leaf. \n\nhttp.pool.maxTotal=1000\nhttp.pool.maxPerRoute=50\nHTTP.pool.maxPerRoute=75    #invalid duplicate nodes at the same path.\n```\n\nAll paths are converted to lower case as different sources have different naming conventions, Env Vars are typically Screaming Snake Case, properties are dot notation, json is camelCase. By normalizing them to lowercase it is easier to merge. However, we do not convert other cases such as camel case into dot notation. It is best to use a consistent case for your configurations. \n\n\n# Retrieving a configuration\n\nTo retrieve a configuration from Gestalt we need the path to the configuration as well as what type of class.\n\n### getConfig path options\nGestalt is **not case sensitive**. Since Gestalt interops between Environment Variables and other sources with various cases, all strings in Gestalt are normalized to a lower case.\nBy default, Gestalt uses dot notation and allows indexing into arrays using a '[0]'.  \nIf you want to use a different path style you can provide your own [custom lexer](#relaxed-path-parsing-to-support-all-case-paths) to Gestalt. The SentenceLexer is used to convert the path passed to the Gestalt getConfig interface into tokens that Gestalt can use to navigate to your sub node.\n\n```java\n  // load a whole class, this works best with pojo's\n  HttpPool pool = gestalt.getConfig(\"http.pool\", HttpPool.class);\n  // or get a specific config value from a class\n  short maxTotal  gestalt.getConfig(\"HTTP.pool.maxTotal\", Short.class);\n  // get with a default if you want a fallback from code\n  long maxConnectionsPerRoute = gestalt.getConfig(\"http.Pool.maxPerRoute\", 24, Long.class);\n\n  // get a list of Host objects, or an PlaceHolder collection if there is no hosts found.\n  LinkedList\u003cHost\u003e hosts = gestalt.getConfig(\"db.hosts\", new LinkedList(), new TypeCapture\u003cLinkedList\u003cHost\u003e\u003e() {});\n\n  // Get a class at a specific list index. \n  Host host = gestalt.getConfig(\"db.hosts[2]\", Host.class);\n  // get a value of a class from a specific list index.\n  String password = gestalt.getConfig(\"db.hosts[2].password\", String.class);\n```\n\nWhen decoding a path we do use path mappers to try different cases, but that only applies to the sub-tree starting at the path asked for. So if you call `gestalt.getConfig(\"http.pool\", HttpPool.class)` it will try and map the cases for nodes under path `http.pool` but not the nodes in the path `http-pool`. \n\n\n```java\n  // given the record. \n  public record HttpPool(String poolSize, int timeout) {}\n```\nAnd the properties: \n```properties\nbooking.service.pool.size = 10\nbooking-service.timeout = 10\n```\n\nWhen getting the config will fail, because `booking.service` will not match `booking-service` so it will not find the timeout.\n```java\n  HttpPool pool = gestalt.getConfig(\"booking.service\", HttpPool.class);\n```\n\nBut given the properties\n```properties\nbooking.service.pool.size = 10\nbooking.service.timeout = 10\n```\n\nThese calls will succeed. When decoding `poolSize` it will first try a lowercase match of `poolsize`, but will not find the node, so it will try `pool.size` for a combined path of `booking.service.pool.size` which it will find. \n```java\n  HttpPool pool = gestalt.getConfig(\"booking.service\", HttpPool.class);\n```\n\n### Retrieving Primitive and boxed types\nGetting primitive and boxed types involves calling Gestalt and providing the class of the type you are trying to retrieve. \n\n```java\nShort myShortWrapper = gestalt.getConfig(\"http.pool.maxTotal\", Short.class);\nshort myShort = gestalt.getConfig(\"http.pool.maxTotal\", short.class);\nString serviceMode = gestalt.getConfig(\"serviceMode\", String.class);\n```\n\nGestalt will automatically decode and provide the value in the type you requested. \n\n## Retrieving Complex Objects\n\nTo retrieve a complex object, you need to pass in the class for Gestalt to return. Gestalt will automatically use reflection to create the object, determine all the fields in the requested class, and then lookup the values in the configurations to inject into the object. It will attempt to use the setter fields first, then fallback to directly setting the fields.\n\nThere are two configuration options that allow you to control when errors are thrown when decoding complex objects.\n\n```java\nHttpPool pool = gestalt.getConfig(\"http.pool\", HttpPool.class);\n```\n\n### `treatMissingValuesAsErrors`\n\nTreat missing field values in an object, proxy, record, or data object as errors. This will cause the API to either throw errors or return an empty optional.\n\n- If this is `true`, any time a value that is not discretionary is missing, it will fail and throw an exception.\n- If this is `false`, a missing value will be returned as `null` or the default initialization. `Null` for objects and `0` for primitives.\n\n### `treatMissingDiscretionaryValuesAsErrors`\n\nTreat missing discretionary values (optional, fields with defaults, fields with default annotations) in an object, proxy, record, or data object as errors.\n\n- If this is `false`, you will be able to get the configuration with default values or an empty Optional.\n- If this is `true`, if a field is missing and would have had a default, it will fail and throw an exception.\n\n\n### `@Nullable` annotations\n\nIf a field or method is annotated with a `@Nullable` annotation, it will treat a missing value as a discretionary value. So as long as `treatMissingDiscretionaryValuesAsErrors` is not enabled, `@Nullable` fields will allow null values without throwing errors.\n\nThere are multiple `@Nullable` annotations and for this to work the annotations must use `@Retention(RetentionPolicy.RUNTIME)` so the annotation is available at runtime for Gestalt. \nOne good library to use is `jakarta.annotation:jakarta.annotation-api` that has a `@Nullable` with `@Retention(RetentionPolicy.RUNTIME)`.\n\n#### Examples of required and discretionary fields. \n\nHere are some examples of required and discretionary fields and which setting can control if they are treated as errors or allowed.\n\n```java\npublic class DBInfo {\n  // discretionary value controlled by treatMissingValuesAsErrors\n  private Optional\u003cInteger\u003e port;                   // default value Optional.empty()\n  private String uri = \"my.sql.db\";                 // default value \"my.sql.db\"\n  private  @Config(defaultVal = \"100\") Integer connections; // default value 100\n\n  // required value controlled by treatMissingDiscretionaryValuesAsErrors\n  private String password;                         // default value null\n}\n\npublic interface DBInfoInterface {\n  Optional\u003cInteger\u003e getPort();                      // default value Optional.empty()\n  default String getUri() {                         // default value \"my.sql.db\"\n     return  \"my.sql.db\";\n  }\n  @Config(defaultVal = \"100\")\n  Integer getConnections();                         // default value 100\n\n  // required value controlled by treatMissingDiscretionaryValuesAsErrors\n  String getPassword();                            // default value null\n}\n\npublic record DBInfoRecord(\n  // discretionary value controlled by treatMissingDiscretionaryValuesAsErrors\n  @Config(defaultVal = \"100\") Integer connections,  // default value 100\n  Optional\u003cInteger\u003e port,                           // default value Optional.empty()\n  \n  // required value controlled by treatMissingDiscretionaryValuesAsErrors\n  String uri,                                      // default value null\n  String password                                  // default value null\n) {}\n```\n\n```kotlin\ndata class DBInfoDataDefault(\n  // discretionary value controlled by treatMissingValuesAsErrors\n    var port: Int?,                                 // default value null\n    var uri: String = \"my.sql.db\",                  // default value \"my.sql.db\"\n    @Config(defaultVal = \"100\")  var connections: Integer, // default value 100\n\n    // required value cam not disable treatMissingDiscretionaryValuesAsErrors and allow nulls. \n    var password: String,                           // required, can not be null.   \n)\n```\n\n### Retrieving Interfaces\nTo get an interface you need to pass in the interface class for gestalt to return.\nGestalt will use a proxy object when requesting an interface. When you call a method on the proxy it will look up the similarly named property, decode and return it. \n\n```java\niHttpPool pool = gestalt.getConfig(\"http.pool\", iHttpPool.class);\n```\n\n### Retrieving Generic objects\nTo get an interface you need to pass in a TypeCapture with the Generic value of the class for gestalt to return.\nGestalt supports getting Generic objects such as Lists, Maps or Sets. However, due to type erasure we need to capture the type using the TypeCapture class. The Generic can be any type Gestalt supports decoding such as a primitive wrapper or an Object.    \n\n```java\nList\u003cHttpPool\u003e httpPoolList = gestalt.getConfig(\"http.pool\", new TypeCapture\u003c\u003e() { });\nvar httpPoolMap = gestalt.getConfig(\"http.pool\", new TypeCapture\u003cMap\u003cString, HttpPool\u003e\u003e() { });\n```\n\nGestalt supports multiple varieties of List such as AbstractList, CopyOnWriteArrayList, ArrayList, LinkedList, Stack, Vector, and SequencedCollection. If asked for a List it will default to an ArrayList.\nGestalt supports multiple varieties of Maps such as HashMap, TreeMap, ArrayList, LinkedHashMap and SequencedMap. If asked for a Map it will default to an HashMap.\nGestalt supports multiple varieties of Sets such as HashSet, TreeSet, LinkedHashSet, LinkedHashMap and SequencedSet. If asked for a Set it will default to an HashSet.\n\n#### Config Data Type\nFor non-generic classes you can use the interface that accepts a class `HttpPool pool = gestalt.getConfig(\"http.pool\", HttpPool.class);`, for Generic classes you need to use the interface that accepts a TypeCapture `List\u003cHttpPool\u003e pools = gestalt.getConfig(\"http.pools\", Collections.emptyList(),\nnew TypeCapture\u003cList\u003cHttpPool\u003e\u003e() {});` to capture the generic type. This allows you to decode Lists, Sets and Maps with a generic type.\n\nThere are multiple ways to get a configuration with either a default, an Optional or the straight value. With the default and Optional Gestalt will not throw an exception if there is an error, instead returning a default or an PlaceHolder Option and log any warnings or errors.\n\n\n#### Tags\nThe API also supports tagged configuration, where providing a tag will retrieve configs that match the specific tags or fallback to the default of no tags.\nYou can implement profiles or environments using tags.\n\n```java\n \u003cT\u003e T getConfig(String path, T defaultVal, Class\u003cT\u003e klass, Tags tags);\n HttpPool pool = gestalt.getConfig(\"http.pool\", HttpPool.class, Tags.of(\"environment\", \"dev\"));\n```\n\nMost configuration sources support tagging them. So you can easily add tags to all properties in a source for your profile or environment. \n\n```java\n Gestalt gestalt = new GestaltBuilder()\n    .addSource(ClassPathConfigSourceBuilder.builder().setResource(\"/default.properties\").build())  // Load the default property files from resources. \n    .addSource(FileConfigSourceBuilder.builder().setFile(devFile).setTags(Tags.of(\"environment\", \"dev\")).build())\n    .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())\n    .build();\n```\n\nThere are utility methods for common tags such as profile and environment. \n```java\nTags.profile(\"test\") == Tags.of(\"profile\", \"test\")\nTags.environment(\"dev\") == Tags.of(\"environment\", \"dev\")\n```\n\n##### Default Tags\n\nYou can set a default tag in the gestalt builder. The default tags are applied to all calls to get a gestalt configuration when tags are not provided. If the caller provides tags they will be used and the default tags will be ignored.  \n```java\n  Gestalt gestalt = new GestaltBuilder()\n    .addSource(ClassPathConfigSourceBuilder.builder().setResource(\"/default.properties\").build())  // Load the default property files from resources. \n    .addSource(FileConfigSourceBuilder.builder().setFile(devFile).setTags(Tags.profile(\"dev\").build()))\n    .addSource(FileConfigSourceBuilder.builder().setFile(testFile).setTags(Tags.profile(\"test\").build()))\n    .setDefaultTags(Tags.profile(\"dev\"))\n    .build();\n    \n  // has implicit Tags of Tags.profile(\"dev\") that is applied as the default tags, so it will use values from the devFile.\n  HttpPool pool = gestalt.getConfig(\"http.pool\", HttpPool.class);\n  \n  // will use the Tags.profile(\"test\") and ignore the default tags of Tags.profile(\"dev\"), so it will use values from the testFile.\n  HttpPool pool = gestalt.getConfig(\"http.pool\", HttpPool.class, Tags.profile(\"test\")); \n```\n\n##### Config Node Tags Resolution Strategies.\n\nBy default, Gestalt expects tags to be an exact match to select the roots to search. This is configurable by setting a different `ConfigNodeTagResolutionStrategy` in the gestalt builder.\n\n```java\nGestalt gestalt = new GestaltBuilder()\n      .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())\n      .addSource(MapConfigSourceBuilder.builder()\n          .setCustomConfig(configs2)\n          .addTag(Tag.profile(\"orange\"))\n          .addTag(Tag.profile(\"flower\"))\n          .build())\n      .setConfigNodeTagResolutionStrategy(new SubsetTagsWithDefaultTagResolutionStrategy())\n      .build();\n```\n\nYou can implement the interface `ConfigNodeTagResolutionStrategy` to define your own resolution strategy. \n\nThe available strategies are:\n\n| name                                        | Set Theory | Description                                                                                                                                                                                                      |\n|---------------------------------------------|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| EqualTagsWithDefaultTagResolutionStrategy   | Equals     | Will Search two config node roots, the one that is an equal match to the tags and the root with no tags. Then return the config node roots to be searched. Only return the roots if they exist.                  |\n| SubsetTagsWithDefaultTagResolutionStrategy  | Subset     | Will Search for any roots that are a subset of the tags provided with a fallback of the default root. In combination with default tags, this can be used to create a profile system similar to Spring Config.    |\n\n\n##### Tags Merging Strategies.\n\nYou can provide tags to gestalt in two ways, setting the defaults in the gestalt config and passing in tags when getting a configuration. \n\n```java\nGestalt gestalt = new GestaltBuilder()\n.addSource(ClassPathConfigSourceBuilder.builder().setResource(\"/default.properties\").build())  // Load the default property files from resources.\n.addSource(FileConfigSourceBuilder.builder().setFile(devFile).setTags(Tags.profile(\"dev\").build()))\n.addSource(FileConfigSourceBuilder.builder().setFile(testFile).setTags(Tags.profile(\"test\").build()))\n.setDefaultTags(Tags.profile(\"dev\"))\n.build();\n\n// will use the Tags.profile(\"test\") and ignore the default tags of Tags.profile(\"dev\"), so it will use values from the testFile.\nHttpPool pool = gestalt.getConfig(\"http.pool\", HttpPool.class, Tags.profile(\"test\"));\n```\n\nThe default behaviour is to use the provided tags with the `getConfig` and if not provided, fall back to the defaults. \n\nBy passing in the TagMergingStrategy to the GestaltBuilder, you can set your own strategy. \n\n```java\nGestalt gestalt = new GestaltBuilder()\n      .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())\n      .addSource(MapConfigSourceBuilder.builder()\n          .setCustomConfig(configs2)\n          .addTag(Tag.profile(\"orange\"))\n          .addTag(Tag.profile(\"flower\"))\n          .build())\n      .setTagMergingStrategy(new TagMergingStrategyCombine())\n      .build();\n```\n\nThe available strategies are:\n\n| name                           | Set Theory   | Description                                                                         |\n|--------------------------------|--------------|-------------------------------------------------------------------------------------|\n| TagMergingStrategyFallback     | exclusive or | Use the provided tags with `getConfig`, and if not provided use a default fallback. |\n| TagMergingStrategyCombine      | union        | Merge the provided tags with `getConfig`, and the defaults                          |\n\nYou can provide your own strategy by implementing TagMergingStrategy.\n\n\n#### Example\nExample of how to create and load a configuration objects using Gestalt:\n```java\n  public static class HttpPool {\n    public short maxTotal;\n    public long maxPerRoute;\n    public int validateAfterInactivity;\n    public double keepAliveTimeoutMs = 6000; // has a default value if not found in configurations\n    public OptionalInt idleTimeoutSec = 10; // has a default value if not found in configurations\n    public float defaultWait = 33.0F; // has a default value if not found in configurations\n\n    public HttpPool() {\n\n    }\n  }\n\n  public static class Host {\n    private String user;\n    private String url;\n    private String password;\n    private Optional\u003cInteger\u003e port;\n\n    public Host() {\n    }\n\n  // getter and setters ...\n  }\n\n...\n  // load a whole class, this works best with pojo's \n  HttpPool pool = gestalt.getConfig(\"http.pool\", HttpPool.class);\n  // or get a spcific config value\n  short maxTotal = gestalt.getConfig(\"http.pool.maxTotal\", Short.class);\n  // get with a default if you want a fallback from code\n  long maxConnectionsPerRoute = gestalt.getConfig(\"http.pool.maxPerRoute\", 24, Long.class);\n\n\n  // get a list of objects, or an PlaceHolder collection if there is no hosts found.\n  List\u003cHost\u003e hosts = gestalt.getConfig(\"db.hosts\", Collections.emptyList(), \n    new TypeCapture\u003cList\u003cHost\u003e\u003e() {});\n```\n\n\n#### Kotlin\nIn Kotlin you dont need to specify the types if you used the kotlin extension methods provided in `gestalt-kotlin`. It uses inline reified methods that automatically capture the type for you based on return type. If no configuration is found and the type is nullable, it will return null otherwise it will throw an GestaltException.\n\n```kotlin\n  data class HttpPool(\n    var maxTotal: Short = 0,\n    var maxPerRoute: Long = 0,\n    var validateAfterInactivity: Int? = 0,\n    var keepAliveTimeoutMs: Double = 6000.0,\n    var idleTimeoutSec: Short = 10,\n    var defaultWait: Float = 33.0f\n  )\n  // load a kotlin data class\n  val pool: HttpPool = gestalt.getConfig(\"http.pool\")\n  // get a list of objects, or an PlaceHolder collection if there is no hosts found.\n  val hosts: List\u003cHost\u003e = gestalt.getConfig(\"db.hosts\", emptyList())\n```   \n\n\n\n# Annotations\nWhen decoding a Java Bean style class, a record, an interface or a Kotlin Data Class you can provide a custom annotation to override the path for the field as well as provide a default.\nThe field annotation `@Config` takes priority if both the field and method are annotated.\nThe class annotation `@ConfigPrefix` allows the user to define the prefix for the config object as part of the class instead of the `getConfig()` call. If you provide both the resulting prefix is first the path in getConfig then the prefix in the `@ConfigPrefix` annotation.\nFor example using `@ConfigPrefix(prefix = \"connection\")` with `DBInfo pool = gestalt.getConfig(\"db\", DBInfo.class);` the resulting path would be `db.connection`.\n\n```java\n@ConfigPrefix(prefix = \"db\")\npublic class DBInfo {\n    @Config(path = \"channel.port\", defaultVal = \"1234\")\n    private int port;\n\n    public int getPort() {\n        return port;\n    }\n}\n\nDBInfo pool = gestalt.getConfig(\"\", DBInfo.class);\n\n\npublic class DBInfo {\n    private int port;\n\n    @Config(path = \"channel.port\", defaultVal = \"1234\")\n    public int getPort() {\n        return port;\n    }\n}  \n\nDBInfo pool = gestalt.getConfig(\"db.connection\", DBInfo.class);\n```\n\nThe path provided in the annotation is used to find the configuration from the base path provided in the call to Gestalt getConfig.\n\nSo if the base path from gestalt.getConfig is `db.connection` and the annotation is `channel.port` the path the configuration will look for is `db.connection.channel.port`\n\nThe default accepts a string type and will be decoded into the property type using the gestalt decoders. For example if the property is an Integer and the default is \"100\" the integer value will be 100.\n\n# Annotations Configurations\n\nCertain annotations can be applied to a configuration using `@{annotation}`, this will covert the annotation to metadata that can be applied to the node. Then the metadata is used to apply the intended behaviour to the node.\n\nFor example, we can apply the temporary node feature on a node by using the annotation `@{temp:1}`\n```properties\nmy.password=abcdef@{temp:1}\n```\n\n| annotation | parameter                                        | description                                                                                  |\n|------------|--------------------------------------------------|----------------------------------------------------------------------------------------------|\n| temp       | (int) Number of times this temp node can be read | restrict the number of times a value can be read before it is released                       |\n| encrypt    | (boolean) if we should apply to this node        | Encrypts the node in memory.                                                                 |\n| nocache    | (boolean) if we should apply to this node        | Will not cache the node. If a node is part of a object the whole object will not be cached.  |\n| secret     | (boolean) if we should apply to this node        | Treats the node as a secret, so it will not print it out in errors or the debug print.       |\n\n## Trim Whitespace\n\nBy default, white spaces before and after the annotation are trimmed. You can disable this feature using the gestalt builder and setting `setAnnotationTrimWhiteSpace(false)`\n\n```java\nGestaltBuilder builder = new GestaltBuilder();\nGestalt gestalt = builder\n  .addSource(MapConfigSourceBuilder.builder()\n    .setCustomConfig(configs)\n    .build())\n  .setAnnotationTrimWhiteSpace(false)\n  .build();\n```\n\n# Searching for path while Decoding Objects\nWhen decoding a class, we need to know what configuration to lookup for each field. To generate the name of the configuration to lookup, we first find the path as defined in the call to `gestalt.getConfig(\"book.service\", HttpPool.class)` where the path is `book.service`. We do not apply the path mappers to the path, only the config tree notes from the path. Once at the path we check class for any annotations. If there are no annotations, then we search for the fields by exact match. So we look for a config value with the same name as the field. If it is unable to find the exact match, it will attempt to map it to a path based on camel case. Where the camel case words will be separated and converted to Kebab case, Snake case and Dot Notation, then used to search for the configuration.\nThe order is descending based on the priority of the mapper.\n\n| Casing                   | Priority | Class Name            |\n|--------------------------|----------|-----------------------|\n| Camel Case (exact match) | 1000     | StandardPathMapper    |\n| Kebab Case               | 600      | KebabCasePathMapper   |\n| Snake Case               | 550      | SnakeCasePathMapper   |\n| Dot Notation             | 500      | DotNotationPathMapper |\n\nGiven the following class lets see how it is translated to the different casings:\n```java\n// With a class of \npublic static class DBConnection {\n    @Config(path = \"host\")\n    private String uri;\n    private int dbPort;\n    private String dbPath;\n}\n```\n\nKebab Case:\nAll objects in Java use the standard Camel case, however in the config files you can use Kebab case, and if an exact match isnt found it will search for a config variable converting Camel case into Kebab case.\nKebab case or an exact match are preferred as using dot notation could potentially cause some issues as it is parsed to a config tree. Using dot notation you would need to ensure that none of values break the tree rules.\n\n```java\n// Given a config of\n\"users.host\" =\u003e \"myHost\"\n\"users.uri\" =\u003e \"myHost\"\n\"users.dbPort\" =\u003e \"1234\"\n\"users.db-path\" =\u003e \"usersTable\"\n  \n// the uri will map to host\n// the dbPort will map to dbPort using Camel case using exact match.\n// the dbPath will automatically map to db.path using Kebab case.     \nDBConnection connection = gestalt.getConfig(\"users\", TypeCapture.of(DBConnection.class));\n```\n\nSnake Case:\nAll objects in Java use the standard Camel case, however in the config files you can use Snake case, and if an exact match isnt found it will search for a config variable converting Camel case into snake case.\n\n```java\n// Given a config of\n\"users.host\" =\u003e \"myHost\"\n\"users.uri\" =\u003e \"myHost\"\n\"users.dbPort\" =\u003e \"1234\"\n\"users.db_path\" =\u003e \"usersTable\"\n  \n// the uri will map to host\n// the dbPort will map to dbPort using Camel case using exact match.\n// the dbPath will automatically map to db_path using snake case     \nDBConnection connection = gestalt.getConfig(\"users\", TypeCapture.of(DBConnection.class));\n```\n\nDot Notation:\nAll objects in Java use the standard Camel case, however in the config files you can use Dot Notation, and if an exact match isnt found it will search for a config variable converting Camel case into Dot Notation.\nKebab case or an exact match are preferred as using dot notation could potentially cause some issues as it is parsed to a config tree. Using dot notation you would need to ensure that none of values break the tree rules.\n\n```java\n// Given a config of\n\"users.host\" =\u003e \"myHost\"\n\"users.uri\" =\u003e \"myHost\"\n\"users.dbPort\" =\u003e \"1234\"\n\"users.db.path\" =\u003e \"usersTable\"\n  \n// the uri will map to host\n// the dbPort will map to dbPort using Camel case using exact match.\n// the dbPath will automatically map to db.path using dot notation.     \nDBConnection connection = gestalt.getConfig(\"users\", TypeCapture.of(DBConnection.class));\n```\n\n# Kotlin\nFor Kotlin Gestalt includes several extension methods that allow easier use of Gestalt by way of reified functions to better capture the generic type information.\nUsing the extension functions you don't need to specify the type if the return type has enough information to be inferred. If nothing is found it will throw a GestaltException unless the type is nullable, then it will return null.\n```kotlin\n  val pool: HttpPool = gestalt.getConfig(\"http.pool\")\n  val hosts: List\u003cHost\u003e = gestalt.getConfig(\"db.hosts\", emptyList())\n```\n| Gestalt Version  | Kotlin Version |\n|------------------|----------------|\n| 0.35.0 +         | 2.1            |\n| 0.25.0 +         | 1.9            |\n| 0.17.0 +         | 1.8            |\n| 0.13.0 to 0.16.6 | 1.7            |\n| 0.10.0 to 0.12.0 | 1.6            |\n| 0.9.0 to 0.9.3   | 1.5            |\n| 0.1.0 to 0.8.1   | 1.4            |\n\n# Node Substitution (include nodes)\nUsing the `$include` keyword as part of a config path, you can include the referenced config node tree into the path provided. By default, the node is merged into the provided node under the current node as defaults that will be overridden. You can control the order of the nodes, by including a number where \u003c 0 is included below the current node and \u003e 0 is included above the current node. The root node is always 0. Having two nodes share the same order is undefined. For example: `$include:-1` for included under the current node, and `$include:1` for included over the current node. \nIf you are included multiple nodes each node must have an order, or the results are undefined, and some includes may be lost. \n\nYou can include into the root or any sub node. It also supports nested include. \n\nThe include node must provide a source that is used to determine how to include the source. Each source accepts different parameters that can be provided in the form of a key value with a comma separated list. One of the key value pairs must be `source` that is used to determine the source type. \nFor example a classPath source with the resource `includes.properties` would look like:\n\n```properties\n$include=source=classPath,resource=includes.properties\n```\n\nExample of include a classPath Node into a sub path with properties file `imports.properties`. \n```properties\nb=b changed\nc=c\n```\n\nIn the first example we include the loaded file node with default settings of order -1 `$include:-1`, where the root node is always order 0. So the node will be loaded under the current root nodes so will provide defaults that will be overwritten. \n```java\n  Map\u003cString, String\u003e configs = new HashMap\u003c\u003e();\n  configs.put(\"a\", \"a\");\n  configs.put(\"b\", \"b\");\n  configs.put(\"$include\", \"source=classPath,resource=includes.properties\");\n  \n \n  Gestalt gestalt = new GestaltBuilder()\n      .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())\n      .build();\n  \n  gestalt.loadConfigs();\n  \n  Assertions.assertEquals(\"a\", gestalt.getConfig(\"a\", String.class));\n  Assertions.assertEquals(\"b\", gestalt.getConfig(\"b\", String.class));\n  Assertions.assertEquals(\"c\", gestalt.getConfig(\"c\", String.class));\n```\nThat is why we don't see `b=b changed` as it will be overwritten by `b=b`, but we still see `c=c` as it was in the included defaults and not overwritten. \n\n\nIn this second example we include the node with `$include:1`. Since the root node is always order 0, the included nodes will override the root. \n```java\n  Map\u003cString, String\u003e configs = new HashMap\u003c\u003e();\n  configs.put(\"a\", \"a\");\n  configs.put(\"b\", \"b\");\n  configs.put(\"$include:1\", \"source=classPath,resource=includes.properties\");\n\n  Gestalt gestalt = new GestaltBuilder()\n      .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())\n      .build();\n\n  gestalt.loadConfigs();\n\n  Assertions.assertEquals(\"a\", gestalt.getConfig(\"a\", String.class));\n  Assertions.assertEquals(\"b changed\", gestalt.getConfig(\"b\", String.class));\n  Assertions.assertEquals(\"c\", gestalt.getConfig(\"c\", String.class));\n```\nThat is why we see `b=b changed` as it is overwritten the root `b=b`.\n\n\nIn the final example, we include the loaded file node in the sub path `sub`.\n```java\n  Map\u003cString, String\u003e configs = new HashMap\u003c\u003e();\n  configs.put(\"a\", \"a\");\n  configs.put(\"b\", \"b\");\n  configs.put(\"sub.a\", \"a\");\n  configs.put(\"sub.$include:1\", \"source=classPath,resource=includes.properties\");\n  \n  Gestalt gestalt = new GestaltBuilder()\n    .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())\n    .build();\n  \n  gestalt.loadConfigs();\n  \n  Assertions.assertEquals(\"a\", gestalt.getConfig(\"a\", String.class));\n  Assertions.assertEquals(\"b\", gestalt.getConfig(\"b\", String.class));\n  Assertions.assertEquals(\"a\", gestalt.getConfig(\"sub.a\", String.class));\n  Assertions.assertEquals(\"b changed\", gestalt.getConfig(\"sub.b\", String.class));\n  Assertions.assertEquals(\"c\", gestalt.getConfig(\"sub.c\", String.class));\n```\nAs you can see the nodes from the file `includes.properties` were included in the sub path `sub`. As can bee seen with `sub.b = b changed` and `sub.c = c`.\n\n\nSupported substitution sources:\n\n| Source Type | Module               | Parameter          | Description                                                                                                                                                                                                                                                                                     |\n|-------------|----------------------|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| classPath   | gestalt-core         | resource           | The name of the classpath resource to load.                                                                                                                                                                                                                                                     |\n| node        | gestalt-core         | path               | Load an node at the given path into the current node.                                                                                                                                                                                                                                           |\n| env         | gestalt-core         | failOnErrors       | If we should fail on errors. Since Env Vars may not always conform to Gestalt expectations we can disable the errors and make it more lenient while loading Env Vars.                                                                                                                           |\n|             |                      | prefix             | Only include Env Vars that match the prefix.                                                                                                                                                                                                                                                    |\n|             |                      | ignoreCaseOnPrefix | When matching the prefix should it ignore case.                                                                                                                                                                                                                                                 |\n|             |                      | removePrefix       | If we should remove the prefix after matching.                                                                                                                                                                                                                                                  |\n| file        | gestalt-core         | file               | Load a file at a given location to the current node.                                                                                                                                                                                                                                            |\n|             |                      | path               | Load a file as a path at a given location to the current node.                                                                                                                                                                                                                                  |\n| k8Secret    | gestalt-core         | path               | The directory to scan for kubernetes secrets.                                                                                                                                                                                                                                                   |\n|             |                      | file               | The file directory to scan for kubernetes secrets.                                                                                                                                                                                                                                              |\n| system      | gestalt-core         | failOnErrors       | If we should fail on errors. Since System Variables may not always conform to Gestalt expectations we can disable the errors and make it more lenient while loading System Vars.                                                                                                                |\n| s3          | gestalt-aws          | Module Config      | To use S3 with the include node feature you must register an `S3Client` via the AWSModuleConfig: ```Gestalt gestalt = builder.addModuleConfig(AWSBuilder.builder().setRegion(\"us-east-1\").setS3Client(s3Client).build()).build();```                                                            |\n|             |                      | bucket             | The S3 bucket to search in.                                                                                                                                                                                                                                                                     |\n|             |                      | key                | The Key of the config file to load.                                                                                                                                                                                                                                                             |\n| blob        | gestalt-azure        | Module Config      | To use Azure Blob with the include node feature you must register an `BlobClient` or a `StorageSharedKeyCredential` via the AzureModuleBuilder : ```Gestalt gestalt = builder.addModuleConfig(AzureModuleBuilder.builder().setBlobClient(blobClient).build())).build();```                      |\n|             |                      | endpoint           | Azure endpoint to access the blob storage.                                                                                                                                                                                                                                                      |\n|             |                      | container          | Azure Container containing the blob.                                                                                                                                                                                                                                                            |\n|             |                      | blob               | The blob with the file.                                                                                                                                                                                                                                                                         |\n| git         | gestalt-git          | Module Config      | When accessing private repos you must register the `GitModuleConfig` with Gestalt. ```Gestalt gestalt = new GestaltBuilder().addModuleConfig(GitModuleConfigBuilder.builder().setCredentials(new UsernamePasswordCredentialsProvider(userName, password)).build()).build(); ```                 |\n|             |                      | repoURI            | Where to locate the repo                                                                                                                                                                                                                                                                        |\n|             |                      | branch             | What branch to find the config files.                                                                                                                                                                                                                                                           |\n|             |                      | configFilePath     | The subpath in the repo URI to find the config file.                                                                                                                                                                                                                                            |\n|             |                      | localRepoDirectory | Where to save the git files Gestalt Syncs.                                                                                                                                                                                                                                                      |\n| gcs         | gestalt-google-cloud | Module Config      | To use GCS with the include node feature you can register a `Storage` client via the GoogleModuleConfig : ```Gestalt gestalt = builder.addModuleConfig(GoogleModuleConfigBuilder.builder().setStorage(storage).build())).build();```. Otherwise it will fallback to the default storage client. |\n|             |                      | bucketName         | What bucket to find the config files.                                                                                                                                                                                                                                                           |\n|             |                      | objectName         | The specific config file to include.                                                                                                                                                                                                                                                            |\n\n# String Substitution\nGestalt supports string substitutions using `${}` at load time on configuration properties to dynamically modify configurations.\n\nFor example if we have a properties file with a Database connection you don't want to save your usernames and passwords in the properties files. Instead, you want to inject the username and passwords as Environment Variables.\n\n```properties\ndb.user=${DB_USER}\ndb.password=${DB_PASSWORD}\n```\n\nYou can use multiple string replacements within a single string to build a configuration property.\n\n```properties\ndb.uri=jdbc:mysql://${DB_HOST}:${DB_PORT}/${environment}\n```\n\n### Load time vs run time\nLoad time `${}` substitutions are evaluated when we load the configurations and build the config tree. This is done once on `gestalt.load()` then all results are cached in the config tree and returned. \nRun time `#{}` substitutions are evaluated at runtime when you call `gestalt.getConfig(...);`, the results are not cached and each time you call `gestalt.getConfig(...);` you will re-evaluate the value.\n\nIt is recommended to use Load time `${}` substitutions in the vast majority of cases as it is more performant. The main use case for run time `#{}` substitutions is for values you expect to change from one call to the next, such as wanting a different random number each time you call `gestalt.getConfig(...);`.\n\nAside from evaluated time, the syntax and use of both `${}` and `#{}` are otherwise identical, you can mix and match them as needed. \n\n### Specifying the Transformer\nYou can specify the substitution in the format ${transform:key} or ${key}. If you provide a transform name it will only check that one transform. Otherwise, it will check all the Transformer annotated with a `@ConfigPriority` in descending order and will return the first matching value.\nUnlike the rest of Gestalt, this is case-sensitive, and it does not tokenize the string (except the node transform). The key expects an exact match, so if the Environment Variable name is DB_USER you need to use the key DB_USER. Using db.user or db_user will not match.\n\n```properties\ndb.uri=jdbc:mysql://${DB_HOST}:${map:DB_PORT}/${sys:environment}\n```\n\n### Defaults for a Substitution\nYou can provide a default for the substitution in the format `${transform:key:=default}` or `${key:=default}`. If you provide a default it will use the default value in the event that the key provided cant be found\n\n```properties\ndb.uri=jdbc:mysql://${DB_HOST}:${map:DB_PORT:=3306}/${environment:=dev}\n```\n\nUsing nested substitution, you can have a chain of defaults. Where you can fall back from one source to another. \n\n```properties\ntest.duration=${sys:duration:=${env:TEST_DURATION:=120}}\n```\nIn this example, it will first try the system variable `duration`, then the Environment Variable `TEST_DURATION` and finally if none of those are found, it will use the default `120`\n\n### Escaping a Substitution\nYou can escape the value with '\\' like `\\${my text}` to prevent the substitution. In Java you need to write `\\\\` to escape the character in a normal string but not in a Text block\nIn nested substitutions you should escape both the opening token `\\${` and the closing token `\\}` to be clear what is escaped, otherwise you may get undetermined results. \n\n```properties\nuser.block.message=You are blocked because \\\\${reason\\\\}\n```\n\n\n### Nested Substitutions\nGestalt supports nested and recursive substitutions. Where a substitution can happen inside another substitution and the results could trigger another substitution.\nPlease use nested substitution sparingly, it can get very complex and confusing quickly. \nUsing these variables:\n\nEnvironment Variables:\n```properties\nDB_HOST=cloudHost\nenvironment=dev\n```\n\nSystem Variables:\n```properties\nDB_HOST=localHost\nenvironment=test\n```\n\nMap Variable:\n```properties\nDB_TRANSFORM=sys\nDB_PORT=13306\n```\nconfig source:\n```properties\ndb.uri=jdbc:mysql://${${DB_TRANSFORM}:DB_HOST}:${map:DB_PORT}/${sys:environment}\n```\n\nThis will resolve ${DB_TRANSFORM} =\u003e `sys`\nthen resolve ${sys:DB_HOST} =\u003e `localHost`\nFor a configuration value of `db.uri=jdbc:mysql://localHost:13306/test`\n\n\nNested substitution resolving to a nested substitution.\nGiven properties:\n```properties\nthis.path = greeting\nyour.path = ${this.path}\nmy.path.greeting = good day\n```\n\nAnd a string to Substitute:\n`\"${my.path.${your.path}}\"`\n\nthe result is `good day`\n\n`${your.path}` resolves to `${this.path}`\n`${this.path}` is then resolved to `greeting`\nAnd finally the path `my.path.greeting` is resolved to `good day`\n\n### Provided Transformers\n| keyword      | priority | source                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |\n|--------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| env          | 100      | Environment Variables                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |\n| envVar       | 100      | **Deprecated** Environment Variables                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| sys          | 200      | Java System Properties                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |\n| map          | 400      | A custom map provided to the constructor                                                                                                                                                                                                                                                                                                                                                                                                                                                                |\n| node         | 300      | map to another leaf node in the configuration tree                                                                                                                                                                                                                                                                                                                                                                                                                                                      |\n| random       | n/a      | provides a random value                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |\n| base64Decode | n/a      | decode a base 64 encoded string                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |\n| base64Encode | n/a      | encode a base 64 encoded string                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |\n| classpath    | n/a      | load the contents of a file on the classpath into a string substitution.                                                                                                                                                                                                                                                                                                                                                                                                                                |\n| dist100      | n/a      | Use a comma-separated list, where each element is a colon-separated pair of a threshold and its corresponding value. If an element has no threshold, it is treated as the default. For example, the format `10:red,30:green,blue` defines ranges and outcomes for random distributions: numbers from `1 to 10` correspond to `red`, `11 to 30` correspond to `green`, and all numbers above `30` `default` to `blue`. This is best used with runtime evaluation using `#{dist100:10:red,30:green,blue}` |\n| file         | n/a      | load the contents of a file into a string substitution                                                                                                                                                                                                                                                                                                                                                                                                                                                  |\n| urlDecode    | n/a      | URL decode a string                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| urlEncode    | n/a      | URL encode a string                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| awsSecret    | n/a      | An AWS Secret is injected for the secret name and key. Configure the AWS Secret by registering a AWSModuleConfig using the AWSBuilder.   ```Gestalt gestalt = builder.addModuleConfig(AWSBuilder.builder().setRegion(\"us-east-1\").build()).build();```                                                                                                                                                                                                                                                  |\n| azureSecret  | n/a      | An Azure Secret is injected for the secret name and key. Configure the Azure Secret by registering a AzureModuleConfig using the AzureModuleBuilder.   ```Gestalt gestalt = builder.addModuleConfig(AzureModuleBuilder.builder().setKeyVaultUri(\"test\").setCredential(tokenCredential)).build();```                                                                                                                                                                                                     |\n| gcpSecret    | n/a      | A Google Cloud Secret given the key provided. Optionally configure the GCP Secret by registering an GoogleModuleConfig using the GoogleBuilder, or let google use the defaults.  ``` Gestalt gestalt = builder.addModuleConfig(GoogleBuilder.builder().setProjectId(\"myProject\").build()).build()```                                                                                                                                                                                                    |\n| vault        | n/a      | A vault Secret given the key provided. Configure the Vault Secret by registering an VaultModuleConfig using the VaultBuilder.  ``` Gestalt gestalt = builder.addModuleConfig(VaultBuilder.builder().setVault(vault).build()).build()```. Uses the io.github.jopenlibs:vault-java-driver project to communicate with vault                                                                                                                                                                               |\n\n\n### Random String Substitution\nTo inject a random variable during config node processing you can use the format ${random:type(origin, bound)}\nThe random value is generated while loading the config, so you will always get the same random value when asking gestalt.\n\n```properties\ndb.userId=dbUser-${random:int(5, 25)}\napp.uuid=${random:uuid}\n```\n\n#### Random Options supported:\n| data type | format                | notes                                                |\n|-----------|-----------------------|------------------------------------------------------|\n| byte      | byte                  | a random byte of data base 64 encoded                |\n| byte      | byte(length)          | random bytes of provided length base 64 encoded      |\n| int       | int                   | a random int of all possible int values              |\n| int       | int(max)              | a random int from 0 to the max value provided        |\n| int       | int(origin, bound)    | a random int between origin and bound                |\n| long      | long                  | a random long of all possible long values            |\n| long      | long(max)             | a random long from 0 to the max value provided       |\n| long      | long(origin, bound)   | a random long between origin and bound               |\n| float     | float                 | a random float between 0 and 1                       |\n| float     | float(max)            | a random float from 0 to the max value provided      |\n| float     | float(origin, bound)  | a random float between origin and bound              |\n| double    | double                | a random double of all possible long values          |\n| double    | double(max)           | a random double from 0 to the max value provided     |\n| double    | double(origin, bound) | a random double between origin and bound             |\n| boolean   | boolean               | a random boolean                                     |\n| string    | string                | a random string of characters a-z of length 1        |\n| string    | string(length)        | a random string of characters a-z of length provided |\n| char      | char                  | a random char of characters a-z                      |\n| uuid      | uuid                  | a random uuid                                        |\n* Note: The formats in the table would need to be embedded inside of ${random:format} so byte(length) would be ${random:byte(10)}\n\n\n# Tags\nWhen adding a config source you are able to apply zero or more Tags to the source. Those tags are then applied to all configuration within that source. Tags are optional and can be omitted.  \nWhen retrieving the config it will first search for an exact match to the tags, if provided, then search for the configs with no tags. It will then merge the results.\nIf you provide 2 tags in the source, when retrieving the configuration you must provide those two exact tags.\n\n```java\n  // head.shot.multiplier = 1.3\n// max.online.players = 32\n    ConfigSourcePackage pveConfig = ClassPathConfigSourceBuilder.builder().setResource(\"/test-pve.properties\").setTags(Tags.of(\"mode\", \"pve\")).build();\n\n    // head.shot.multiplier = 1.5\n    ConfigSourcePackage pvpConfig = ClassPathConfigSourceBuilder.builder().setResource(\"/test-pvp.properties\").setTags(Tags.of(\"mode\", \"pvp\")).build();\n\n    // head.shot.multiplier = 1.0\n    // gut.shot.multiplier = 1.0\n    ConfigSourcePackage defaultConfig = ClassPathConfigSourceBuilder.builder().setResource(\"/test.properties\").setTags(Tags.of()).build(); // Tags.of() can be omitted\n          \n    Gestalt gestalt = builder\n    .addSource(pveConfig)\n    .addSource(pvpConfig)\n    .addSource(defaultConfig)\n    .build();\n\n    // retrieving \"head.shot.multiplier\" values change depending on the tag. \n    float pvpHeadShot = gestalt.getConfig(\"head.shot.multiplier\", Float.class, Tags.of(\"mode\", \"pve\"));  // 1.3\n  float pveHeadShot = gestalt.getConfig(\"head.shot.multiplier\", Float.class, Tags.of(\"mode\", \"pvp\"));  // 1.5\n  float coopHeadShot = gestalt.getConfig(\"head.shot.multiplier\", Float.class, Tags.of(\"mode\", \"coop\"));  // 1.0 fall back to default\n  float defaultHeadShot = gestalt.getConfig(\"head.shot.multiplier\", Float.class);  // 1.0\n\n  // Gut shot is only defined in the default, so it will always return the default. \n  float pvpGutShot = gestalt.getConfig(\"gut.shot.multiplier\", Float.class, Tags.of(\"mode\", \"pve\"));  // 1.0\n  float pveGutShot = gestalt.getConfig(\"gut.shot.multiplier\", Float.class, Tags.of(\"mode\", \"pvp\"));  // 1.0\n  float coopGutSoot = gestalt.getConfig(\"gut.shot.multiplier\", Float.class, Tags.of(\"mode\", \"coop\"));  // 1.0\n  float defaultGutShot = gestalt.getConfig(\"gut.shot.multiplier\", Float.class);  // 1.0\n\n  // Max online players is only defined in the pvp, so it will only return with the pvp tags. \n  float pvpGutShot = gestalt.getConfig(\"gut.shot.multiplier\", Float.class, Tags.of(\"mode\", \"pve\"));  // 32\n  float pveGutShot = gestalt.getConfig(\"gut.shot.multiplier\", Float.class, Tags.of(\"mode\", \"pvp\"));  // not found\n  float coopGutSoot = gestalt.getConfig(\"gut.shot.multiplier\", Float.class, Tags.of(\"mode\", \"coop\"));  // not found\n  float defaultGutShot = gestalt.getConfig(\"gut.shot.multiplier\", Float.class);  // not found\n```\n\n* **Note**: The config node processor string replacement doesn't accept tags, so it will always replace the configs with the tag-less ones.\n\n\n## Supported config sources\n| Config Source                 | module                                                               | Details                                                                                                                                                                                                                                                                                                                                                            |\n|-------------------------------|----------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| BlobConfigSource              | [`gestalt-azure`](https://search.maven.org/search?q=gestalt-azure)   | Loads a config source from Azure Blob, Must include package com.github.gestalt-config:gestalt-azure:version.                                                                                                                                                                                                                                                       |\n| ClassPathConfigSource         | gestalt-core                                                         | Load a file from the java class path. Uses getResourceAsStream to find and load the InputStream.                                                                                                                                                                                                                                                                   |\n| EnvironmentConfigSource       | gestalt-core                                                         | Loads all Environment Variables in the system. It expects Env Vars to be in screaming snake case, and will parse the \"_\" as a path delimiter.  will convert them to a list of key values from the Env Map for the config loader. You can provide a prefix to only load Environment Variables with the prefix. Then you can choose to keep the prefix or remove it. |\n| FileConfigSource              | gestalt-core                                                         | Loads a file from the local file system. The format for the source will depend on the file extension of the file. For example if it is dev.properties, the format will be properties. Returns a InpuStream for the config loader.                                                                                                                                  |\n| InputStreamConfigSource       | gestalt-core                                                         | Load a configuration from a InputStream. The format for the source will depend on the file extension of the file. For example if it is dev.properties, the format will be properties. Returns a InpuStream for the config loader.                                                                                                                                  |\n| KubernetesSecretConfigSource  | gestalt-core                                                         | Specify a path to search for [kubernetes secrets](https://kubernetes.io/docs/concepts/configuration/secret/) files. The directory is scanned and each file is added to the configuration. The name of the file is treated as the key for configuration and the content of the file is the value for the configuration.                                             |\n| GCSConfigSource               | [`gestalt-google`](https://search.maven.org/search?q=gestalt-google) | Load a config from Google Cloud Storage. Requires a bucketName and a objectName. A google Storage object is optional, otherwise it defaults to the default instance.                                                                                                                                                                                               |\n| GitConfigSource               | [`gestalt-git`](https://search.maven.org/search?q=gestalt-git)       | Syncs a remote repo locally then uses the files to build a configuration. This uses jgit and supports several forms of authentication. See GitConfigSourceTest.java for examples of use.                                                                                                                                                                           |\n| MapConfigSource               | gestalt-core                                                         | Allows you to pass in your own map, it will convert the map into a list of path and value for the config loader.                                                                                                                                                                                                                                                   |\n| StringConfigSource            | gestalt-core                                                         | Takes any string and converts it into a InputStream. You must also provide the format type so we can match it to a loader.                                                                                                                                                                                                                                         |\n| SystemPropertiesConfigSource  | gestalt-core                                                         | Loads the Java System Properties and convert them to a list of key values or the config loader.                                                                                                                                                                                                                                                                    |\n| S3ConfigSource                | [`gestalt-aws`](https://search.maven.org/search?q=gestalt-aws)       | Loads a config source from AWS S3, Must include package com.github.gestalt-config:gestalt-aws:version.                                                                                                                                                                                                                                                             |\n| URLConfigSource               | gestalt-core                                                         | Loads a config source from a URL.                                                                                                                                                                                                                                                                                                                                  |\n\n\n# Config Loader\nEach config loader understands how to load a specific type of config. Often this is associated with a specific ConfigSource. For example the EnvironmentVarsLoader only loads the EnvironmentConfigSource. However, some loaders expect a format of the config, but accept it from multiple sources. For example the PropertyLoader expects the typical java property file, but it can come from any source as long as it is an input stream. It may be the system properties, local file, github, or S3.\n\n| Config Loader         | Formats supported                       | details                                                                                                                                                                                                                                                                                                                                                                                                                                             | module                                                             |\n|-----------------------|-----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|\n| EnvironmentVarsLoader | envVars                                 | Loads Environment Variables from the EnvironmentConfigSource, it expects a list not a InputStream. By default, it splits the paths using a \"_\". You can also disable failOnErrors if you are receiving errors from the environment variables, as you can not always control what is present. By treating Errors as warnings it will not fail if it finds a configuration the parser doesn't understand. Instead it will ignore the specific config. | core                                                               | \n| MapConfigLoader       | mapConfig                               | Loads a user provided Map from the MapConfigSource, it expects a list not a InputStream. By default, it splits the paths using a \".\" and tokenizes arrays with a numeric index as \"[0]\".                                                                                                                                                                                                                                                            | core                                                               | \n| PropertyLoader        | properties, props, and systemProperties | Loads a standard property file from an InputStream. By default, it splits the paths using a \".\" and tokenizes arrays with a numeric index as \"[0]\".                                                                                                                                                                                                                                                                                                 | core                                                               |\n| JsonLoader            | json                                    | Leverages Jackson to load json files and convert them into a ConfigNode tree.                                                                                                                                                                                                                                                                                                                                                                       | [`gestalt-json`](https://search.maven.org/search?q=gestalt-json)   |\n| TomlLoader            | toml                                    | Leverages Jackson to load toml files and convert them into a ConfigNode tree.                                                                                                                                                                                                                                                                                                                                                                       | [`gestalt-toml`](https://search.maven.org/search?q=gestalt-toml)   |\n| YamlLoader            | yml and yaml                            | Leverages Jackson to load yaml files and convert them into a ConfigNode tree.                                                                                                                                                                                                                                                                                                                                                                       | [`gestalt-yaml`](https://search.maven.org/search?q=gestalt-yaml)   |\n| HoconLoader           | config                                  | Leverages com.typesafe:config to load hocon files, supports substitutions.                                                                                                                                                                                                                                                                                                                                                                          | [`gestalt-hocon`](https://search.maven.org/search?q=gestalt-hocon) |\n\nIf you didn't manually add any ConfigLoaders as part of the GestaltBuilder, it will add the defaults. The GestaltBuilder uses the service loader to create instances of the Config loaders. It will configure them by passing in the GestaltConfig to applyConfig.\nTo register your own default ConfigLoaders add them to the builder, or add it to a file in META-INF\\services\\org.github.gestalt.config.loader.ConfigLoader and add the full path to your ConfigLoader\n\nBy default, Gestalt expects Environment Variables to be screaming snake case, but you can configure it to have a different case.  \n\nBy registering a `EnvironmentVarsLoaderModuleConfig` with the `GestaltBuilder` you can customize the Environment Loader. \n\nIn this example it will expect double `__` as delimiter.  \n```java\n GestaltBuilder builder = new GestaltBuilder();\nGestalt gestalt = builder\n  .addSource(EnvironmentConfigSourceBuilder.builder().build())\n  .addModuleConfig(EnvironmentVarsLoaderModuleConfigBuilder\n    .builder()\n    .setLexer(new PathLexer(\"__\"))\n    .build())\n  .build();\n\ngestalt.loadConfigs();\n```\n\nYou can also customize many of the Loaders such as the `YamlLoader`, `TomlLoader`, `JsonLoader` and `HoconLoader` by registering the Module Configs with the builder.  \n\n```java\n GestaltBuilder builder = new GestaltBuilder();\nGestalt gestalt = builder\n  .addSource(ClassPathConfigSourceBuilder.builder().setResource(\"/default.yaml\").build())\n  .addModuleConfig(YamlModuleConfigBuilder.builder()\n    .setObjectMapper(customObjectmapper)\n    .build())\n  .build();\n\ngestalt.loadConfigs();\n```\n\n\n# Decoders\n| Type                | details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |\n|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| Array               | Java primitive array type with any generic class, Can decode simple types from a single comma separated value, or from an array node. You can escape the comma with a \\\\, so the values are not split.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |\n| BigDecimal          |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| BigInteger          |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| Boolean             | Boolean and boolean                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |\n| Byte                | Byte and byte                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |\n| Char                | Char and char                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |\n| ConfigContainer     | A container that caches your config value, and updates it when there is a configuration change.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| Date                | takes a DateTimeFormatter as a parameter, by default it uses DateTimeFormatter.ISO_DATE_TIME                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |\n| Double              | Double and double                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |\n| Duration            | Decodes a duration from either a number or an ISO 8601 standardized string format like \"PT42S\".                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| Enum                |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| File                |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| Float               | Float and float                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| Instant             |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| Integer             | Integer and int                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| List                | a Java list with any Generic class, Can decode simple types from a single comma separated value, or from an array node. You can escape the comma with a \\\\, so the values are not split. Supports multiple varieties of List such as AbstractList, CopyOnWriteArrayList, ArrayList, LinkedList, Stack, Vector, and SequencedCollection. If asked for a List it will default to an ArrayList.                                                                                                                                                                                                                                                                                                        |\n| LocalDate           | Takes a DateTimeFormatter as a parameter, by default it uses DateTimeFormatter.ISO_LOCAL_DATE                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |\n| LocalDateTime       | Takes a DateTimeFormatter as a parameter, by default it uses DateTimeFormatter.ISO_DATE_TIME                                                                                                                                                                                                                                                                                                                                                                                                                 ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgestalt-config%2Fgestalt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgestalt-config%2Fgestalt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgestalt-config%2Fgestalt/lists"}