Configuration
The configuration module reads application settings from HOCON or YAML files, environment variables, Java system
properties, and maps them to typed classes in Kora. The resulting configuration objects become regular dependency
graph components and can be injected into services, clients, servers, and other integrations.
In Kora, application configuration is usually described by an interface annotated with @ConfigSource: the path in
the file points to the section to read, and the interface methods describe required values, optional values, and defaults.
Libraries and reusable configuration shapes use @ConfigValueExtractor, which creates only the extraction rule, while
the concrete path is selected in the library module.
For a step-by-step walkthrough before the reference details, see HOCON Configuration and YAML Configuration.
HOCON¶
Support for HOCON is implemented with Typesafe Config.
HOCON is a JSON-based configuration file format. It is less strict than JSON and supports substitutions, defaults,
and a convenient syntax for nested objects.
services {
foo {
bar = "SomeValue" //(1)!
baz = 10 //(2)!
propRequired = ${REQUIRED_ENV_VALUE} //(3)!
propOptional = ${?OPTIONAL_ENV_VALUE} //(4)!
propDefault = 10
propDefault = ${?NON_DEFAULT_ENV_VALUE} //(5)!
propReference = ${services.foo.bar}Other${services.foo.baz} //(6)!
propArray = ["v1", "v2"] //(7)!
propArrayAsString = "v1, v2" //(8)!
propMap = { //(9)!
"k1" = "v1"
"k2" = "v2"
}
propObject = { //(10)!
p1 = "v1"
p2 = "v2"
}
propObjects = [ //(11)!
{
p1 = "v1"
p2 = "v2"
},
{
p1 = "v3"
p2 = "v4"
}
]
}
}
- String configuration value
- Numeric configuration value
- Required configuration value substituted from the
REQUIRED_ENV_VALUEenvironment variable - Optional configuration value substituted from the
OPTIONAL_ENV_VALUEenvironment variable; if the variable is not found, the configuration value is omitted - Configuration value with a default: the default is specified as
propDefault = 10, andNON_DEFAULT_ENV_VALUE, if found, replaces it - Configuration value assembled from substitutions of other configuration parts with the
Othervalue between them - String list configuration value; the value can be set as an array of strings or as a comma-separated string
- String list configuration value; the value can be set as a comma-separated string or as an array of strings
- Configuration value as a key-value dictionary
- Configuration value as a mapped class
- Configuration value as a list of mapped classes
Values can also reference other configuration keys (self-reference / cross-reference) via ${path}, and environment
variables via ${VAR} (required), ${?VAR} (optional), or a default fallback. All substitutions are resolved after
every layer is merged, so a reference can point at a key defined in another file or in another configuration layer.
Configuration representation in code:
@ConfigSource("services.foo")
public interface FooConfig {
String bar();
Integer baz();
String propRequired();
@Nullable
String propOptional();
Integer propDefault();
String propReference();
List<String> propArray();
List<String> propArrayAsString();
Map<String, String> propMap();
@ConfigValueExtractor
public interface ObjectConfig {
String p1();
String p2();
}
ObjectConfig propObject();
List<ObjectConfig> propObjects();
}
@ConfigSource("services.foo")
interface FooConfig {
fun bar(): String
fun baz(): Int
fun propRequired(): String
fun propOptional(): String?
fun propDefault(): Int
fun propReference(): String
fun propArray(): List<String>
fun propArrayAsString(): List<String>
fun propMap(): Map<String, String>
@ConfigValueExtractor
interface ObjectConfig {
fun p1(): String
fun p2(): String
}
fun propObject(): ObjectConfig
fun propObjects(): List<ObjectConfig>
}
Dependency¶
Dependency build.gradle:
Module:
Dependency build.gradle.kts:
Module:
File¶
By default, the reference.conf and application.conf configuration files are expected.
First, all reference.conf files from the classpath are merged, then application.conf is overlaid on top of the
unresolved reference.conf, and after that the result is resolved and required substitutions are checked.
The application configuration is expected to be in application.conf, while library configuration is expected to be in reference.conf.
HOCON also supports the include directive:
files pulled in through include participate in the same merge and substitution resolution as the main file,
and are tracked by the Config Watcher so that changes in an included file also refresh the graph.
Application file selection priority for HOCON:
- Use the file from
config.resourceif specified (file from theresourcesdirectory) - Use the file from
config.fileif specified (file from the file system) - Use
application.confif present (file from theresourcesdirectory) - Use an empty configuration if none of the above is present
Only one property can be specified at the same time: config.resource or config.file. If both properties are specified,
the application will fail on startup.
YAML¶
Support for YAML is implemented using SnakeYAML.
services:
foo:
bar: "SomeValue" #(1)!
baz: 10 #(2)!
propRequired: ${REQUIRED_ENV_VALUE} #(3)!
propOptional: ${?OPTIONAL_ENV_VALUE} #(4)!
propDefault: ${?NON_DEFAULT_ENV_VALUE:10} #(5)!
propReference: ${services.foo.bar}Other${services.foo.baz} #(6)!
propArray: ["v1", "v2"] #(7)!
propArrayAsString: "v1, v2" #(8)!
propMap: #(9)!
k1: "v1"
k2: "v2"
propObject: #(10)!
p1: "v1"
p2: "v2"
propObjects: #(11)!
- p1: "v1"
p2: "v2"
- p1: "v1"
p2: "v2"
- String configuration value
- Numeric configuration value
- Required configuration value substituted from the
REQUIRED_ENV_VALUEenvironment variable - Optional configuration value substituted from the
OPTIONAL_ENV_VALUEenvironment variable; if the variable is not found, the configuration value is omitted - Configuration value with a default: the default is
10, andNON_DEFAULT_ENV_VALUE, if found, replaces it - Configuration value assembled from substitutions of other configuration parts with the
Othervalue between them - String list configuration value; the value can be set as an array of strings or as a comma-separated string
- String list configuration value; the value can be set as a comma-separated string or as an array of strings
- Configuration value as a key-value dictionary
- Configuration value as a mapped class
- Configuration value as a list of mapped classes
Configuration representation in code:
@ConfigSource("services.foo")
public interface FooConfig {
String bar();
Integer baz();
String propRequired();
@Nullable
String propOptional();
Integer propDefault();
String propReference();
List<String> propArray();
List<String> propArrayAsString();
Map<String, String> propMap();
@ConfigValueExtractor
public interface ObjectConfig {
String p1();
String p2();
}
ObjectConfig propObject();
List<ObjectConfig> propObjects();
}
@ConfigSource("services.foo")
interface FooConfig {
fun bar(): String
fun baz(): Int
fun propRequired(): String
fun propOptional(): String?
fun propDefault(): Int
fun propReference(): String
fun propArray(): List<String>
fun propArrayAsString(): List<String>
fun propMap(): Map<String, String>
@ConfigValueExtractor
interface ObjectConfig {
fun p1(): String
fun p2(): String
}
fun propObject(): ObjectConfig
fun propObjects(): List<ObjectConfig>
}
Dependency¶
Dependency build.gradle:
Module:
Dependency build.gradle.kts:
Module:
File¶
By default, the reference.yaml and application.yaml configuration files are expected.
First, all reference.yaml files from the classpath are merged, then application.yaml is overlaid on top of
reference.yaml, and after that the result is resolved and required substitutions are checked.
The application configuration is expected to be in application.yaml, while library configuration is expected to be in reference.yaml.
Application file selection priority for YAML:
- Use the file from
config.resourceif specified (file from theresourcesdirectory) - Use the file from
config.fileif specified (file from the file system) - Use
application.yamlif present (file from theresourcesdirectory) - Use an empty configuration if none of the above is present
Only one property can be specified at the same time: config.resource or config.file. If both properties are specified,
the application will fail on startup.
Custom configuration¶
A custom configuration maps a configuration file section to a user type. That type can then be injected as a dependency just like any other component.
Application config¶
Use the @ConfigSource annotation to create custom configurations in an application.
It generates a ConfigValueExtractor for the interface and a module that adds the ready configuration object to the
dependency graph. The annotation value points to the section path inside the resulting configuration:
This code sample will add an instance of the FooServiceConfig class to the dependency container, which when created will expect the following kind of configuration:
After that, the FooServiceConfig class can already be used as a dependency in other classes:
Library config¶
Use the @ConfigValueExtractor annotation to create custom configurations in libraries.
It creates a rule for extracting a value from ConfigValue<?>, but does not bind it to a concrete configuration path.
The path is selected in a library module factory method, so the same configuration shape can be reused for different sections.
@ConfigValueExtractor can be used on a Java interface, record, or class, and on a Kotlin interface or data class.
The annotation has the mapNullAsEmptyObject parameter (default: true). When enabled, a missing section is treated
as an empty object: required fields still fail, while optional fields and defaults behave as if an empty section was present.
If mapNullAsEmptyObject = false, a missing section is treated as null for the whole configuration object.
Consider this configuration class:
For the library to provide configuration, implement a factory in a module:
The factory will expect a configuration of the following kind:
Then, after connecting FooLibraryModule in the application, FooLibraryConfig can be used as a dependency in other classes.
Required values¶
By default, all values declared in the configuration are considered required (NotNull) and must be present in the
resulting configuration. If a required value is missing or has the null value, the application will fail while creating
the configuration object.
Optional values¶
If you need to specify a value from the configuration file as optional, you can use this format:
It is suggested to use the @Nullable annotation over the method signature:
@ConfigSource("services.foo")
public interface FooServiceConfig {
@Nullable//(1)!
String bar();
int baz();
}
- Any
@Nullableannotation will do, for examplejavax.annotation.Nullable/jakarta.annotation.Nullable/org.jetbrains.annotations.Nullable.
Use Kotlin null-safety syntax and mark the parameter as nullable:
An Optional<T> return type is also supported (an absent value maps to Optional.empty()), but a @Nullable value
(or a Kotlin nullable type) is the recommended style.
Default values¶
If you need to set a default value in configuration mapping, use a default method:
Relaxed key names¶
Configuration keys are matched with relaxed naming. A method name is compared against the key in the file not only in
its exact form, but also in its kebab-case and snake_case variants. This means a method someBarString() resolves
equally from someBarString, some-bar-string, or some_bar_string in the configuration file, so teams that prefer
kebab-case or snake_case keys can keep their style without renaming methods.
All three key spellings below are read into someBarString():
Recommended style¶
It is usually more convenient to describe configuration as a separate type for a concrete integration or subsystem: an HTTP client, an external service connection, a queue handler, and so on. Such a type should clearly separate required values, optional values, and values that come from environment variables.
In the example below:
baseUrlis a required value from the configuration fileclientNameis an optional value from theORDERS_CLIENT_NAMEenvironment variabletokenis a required value from theORDERS_API_TOKENenvironment variablerequestTimeouthas the2sdefault value and can be overridden by the optionalORDERS_REQUEST_TIMEOUTenvironment variable
This keeps the configuration structure readable: required settings are visible in the configuration type, secrets can be passed through environment variables, and safe defaults stay directly in the configuration file.
Injecting configuration¶
You can inject the base class ru.tinkoff.kora.config.common.Config, which represents the configuration tree and gives
access to values through the get(...) method. The resulting configuration consists of several layers:
- Environment variables
Javasystem properties- Configuration file
Layers are merged in this order: environment variables, then system properties, then the application configuration file. Each next layer overlays the previous one.
Environment variables¶
If you need to inject configuration that contains only environment variables,
use the @Environment annotation as a tag for the configuration class:
System properties¶
If you need to inject configuration that contains only Java system properties,
use the @SystemProperties annotation as a tag for the configuration class:
Configuration file¶
If you need to inject application configuration that consists only of the configuration file,
use the @ApplicationConfig annotation as a tag for the configuration class:
Resulting configuration¶
If you need to inject the complete resulting application configuration, which consists of the configuration file, environment variables and system properties, simply inject the configuration class without a tag:
Reading raw Config values¶
When a raw Config is injected, values are read through the get(...) method, which returns a ConfigValue<?> node
for the requested path. ConfigValue<?> is a sealed type with typed accessors: asString(), asNumber(),
asBoolean(), asObject(), asArray(), and isNull(). If the value has an unexpected type, the accessor throws
ConfigValueExtractionException.
As noted in Recommendations, prefer typed custom configurations over
reading a raw Config.
Use the raw read API only for dynamic or generic access when no other choice and use ValueOf<Config> to avoid component refresh.
Attention
We do not recommend using ru.tinkoff.kora.config.common.Config directly as a dependency in components,
because when configuration is updated, all graph components that use it will be updated as well.
We recommend always creating custom configurations.
Config Watcher¶
By default, Kora has a configuration file watcher that checks the application file for changes and starts dependency
graph refresh if the file changes. The check runs every 1000 milliseconds.
For HOCON, the watcher also tracks files included through include inside the main configuration file.
If such an included file changes, the configuration is reread and the dependency graph is refreshed as well.
The watcher works only for file-based configuration that has a trackable source. If configuration came from a resource inside an archive or was built without an application file, there is nothing on disk to update.
You can disable the watcher by using:
- Environment variable
KORA_CONFIG_WATCHER_ENABLED(default:true) - System property
kora.config.watcher.enabled(default:true)
Supported types¶
Configuration extractors provide an extensive list of supported types that covers most values you may need in custom
configurations. If the standard conversion is not enough, the behavior can be extended with a custom
ConfigValueExtractor<T> component.
List of supported types
- boolean / Boolean
- short / Short
- int / Integer
- long / Long
- double / Double
- float / Float
- double[]
- String
- BigInteger
- BigDecimal
- Period
- Duration
- Size
- Properties
- Pattern
- UUID
- LocalDate
- LocalTime
- LocalDateTime
- OffsetTime
- OffsetDateTime
- ConfigValue.ObjectValue
- Enum (any custom
enum; mapping can be overridden throughtoString()) Optional<T>(whereTis any supported type)List<T>(whereTis any supported type)Set<T>(whereTis any supported type)Map<String, V>orMap<K, V>(whereKandVare supported by corresponding extractors)Either<A, B>(whereAandBare any supported types)
Custom extractor¶
If there is no standard conversion for a type or special parsing logic is required, add a custom
ConfigValueExtractor<T> component. The extract(...) method receives the configuration value as ConfigValue<?>
and must return the ready value of the required type.
If a specific extractor should be used only for one field, specify it through @Mapping:
Duration¶
Duration can be set as a number or a string.
If a number is specified, it is treated as milliseconds.
If a string is specified, the java.time.Duration format is supported, for example PT10S, as well as HOCON style:
500ms10 seconds2 minutes1h1d
Period¶
Period can be set as a number or a string.
If a number is specified, it is treated as days.
If a string is specified, these units are supported:
d/daysw/weeksm/mo/monthsy/years
For example, 7d, 2 weeks, 3mo, or 1 year.
Size¶
Size is a special type that allows specifying byte sizes in a human-friendly notation: according to the
IEEE 1541-2002 standard (binary) or the
SI standard (decimal).
Example values:
1Mb- 1 megabyte (1.000.000bytes)1Mib- 1 mebibyte (1.048.576bytes)1024b- 1024 bytes1024- 1024 bytes
If just a number without a suffix is specified, it is considered that bytes are specified.
Either¶
Either<A, B> lets a single field accept two alternative shapes. The extractor tries the left type A first, and if
extraction fails with any exception, it falls back to the right type B. This is useful when a value may be either a
plain scalar or a structured object.
Both of these forms are valid for the endpoint field:
Use isLeft() / isRight() to check which side was resolved, and left() / right() to read the value.