Instance Builder#
With the io.smallrye.config.ConfigInstanceBuilder API, it is possible to create instances of a configuration interface
programmatically, without requiring a SmallRyeConfig or any configuration source. This is useful for testing, default
configurations, or any scenario where configuration values are known at build time.
Usage#
A configuration interface instance is created with ConfigInstanceBuilder.forInterface():
interface Server {
String host();
int port();
}
Server server = ConfigInstanceBuilder.forInterface(Server.class)
.with(Server::host, "localhost")
.with(Server::port, 8080)
.build();
The builder uses method references to identify which property to set, providing compile-time type safety without
string-based property names. The with method accepts a method reference to the configuration interface member and the
value to set.
Info
The configuration interface does not require the @ConfigMapping annotation to work with the builder. Any valid
configuration interface is accepted.
Primitive Types#
The builder provides dedicated with() overloads for int, long, double, and boolean primitive types. Other
primitive types (byte, short, float, char) are set via the generic with() method using their boxed types:
@ConfigMapping
interface Primitives {
int intValue();
boolean booleanValue();
byte byteValue();
}
Primitives primitives = ConfigInstanceBuilder.forInterface(Primitives.class)
.with(Primitives::intValue, 42)
.with(Primitives::booleanValue, true)
.with(Primitives::byteValue, Byte.valueOf((byte) 1))
.build();
Optional Properties#
The withOptional() method sets optional properties. If an optional property is not set, it defaults to empty:
interface AppConfig {
Optional<String> name();
OptionalInt timeout();
}
AppConfig config = ConfigInstanceBuilder.forInterface(AppConfig.class)
.withOptional(AppConfig::name, "MyApp")
.withOptional(AppConfig::timeout, 30)
.build();
The withOptional method wraps the value in Optional.of(), OptionalInt.of(), OptionalLong.of(), or
OptionalDouble.of() depending on the property type.
Defaults#
Properties annotated with io.smallrye.config.WithDefault are automatically applied when no value is explicitly set
in the builder:
interface ServerDefaults {
@WithDefault("localhost")
String host();
@WithDefault("8080")
int port();
}
ServerDefaults server = ConfigInstanceBuilder.forInterface(ServerDefaults.class).build();
The ServerDefaults#host() will return the value localhost and ServerDefaults#port() will return the value 8080.
Explicitly set values override the @WithDefault annotation:
ServerDefaults server = ConfigInstanceBuilder.forInterface(ServerDefaults.class)
.with(ServerDefaults::host, "0.0.0.0")
.build();
The ServerDefaults#host() will return the value 0.0.0.0 and ServerDefaults#port() will return the value 8080.
Nested Groups#
Nested configuration groups are built separately and composed into the parent builder:
interface AppConfig {
String name();
DatabaseConfig database();
}
interface DatabaseConfig {
String url();
int poolSize();
}
AppConfig config = ConfigInstanceBuilder.forInterface(AppConfig.class)
.with(AppConfig::name, "MyApp")
.with(AppConfig::database, ConfigInstanceBuilder.forInterface(DatabaseConfig.class)
.with(DatabaseConfig::url, "jdbc:h2:mem:test")
.with(DatabaseConfig::poolSize, 10)
.build())
.build();
If a nested group has @WithDefault values for all its members, the nested group instance is automatically built with
those defaults when not explicitly set in the builder.
Collections and Maps#
List, Set, and Map types are set directly with their values:
interface AppConfig {
List<String> hosts();
Map<String, String> labels();
}
AppConfig config = ConfigInstanceBuilder.forInterface(AppConfig.class)
.with(AppConfig::hosts, List.of("host1", "host2"))
.with(AppConfig::labels, Map.of("env", "prod", "region", "us-east"))
.build();
Collections and Maps annotated with @WithDefault are automatically populated when not explicitly set.
Converters#
The builder uses the same converter mechanism as SmallRyeConfig. Converters are discovered via the
java.util.ServiceLoader mechanism, or can be registered explicitly with ConfigInstanceBuilder.registerConverter():
Properties annotated with io.smallrye.config.WithConverter use the specified converter for both @WithDefault value
conversion and any value set via the builder:
interface AppConfig {
@WithDefault("30s")
@WithConverter(DurationConverter.class)
Duration timeout();
@WithConverter(MemorySizeConverter.class)
MemorySize maxBodySize();
}
Info
Converters registered with registerConverter are global and shared across all ConfigInstanceBuilder instances.
Required Properties#
Properties without a @WithDefault are considered required. Calling build() throws a NoSuchElementException if
any required property is missing:
interface Server {
String host();
@WithDefault("8080")
int port();
}
// Throws NoSuchElementException because host is required
ConfigInstanceBuilder.forInterface(Server.class).build();
// Works because host is provided
ConfigInstanceBuilder.forInterface(Server.class)
.with(Server::host, "localhost")
.build();
Builder Reuse#
A builder can be used to produce multiple instances. Each build() call creates a new independent instance:
ConfigInstanceBuilder<Server> builder = ConfigInstanceBuilder.forInterface(Server.class)
.with(Server::host, "localhost")
.with(Server::port, 8080);
Server first = builder.build();
Server second = builder.build();
The first and second instances are equal but not the same object. Values set after a build() call affect only
subsequent builds.