4.8 Immutable Configuration
Since 1.3, Micronaut supports the definition of immutable configuration.
There are two ways to define immutable configuration. The preferred way is to define an interface annotated with @ConfigurationProperties. For example:
@ConfigurationProperties Example
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.bind.annotation.Bindable;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Optional;
@ConfigurationProperties("my.engine") (1)
public interface EngineConfig {
@Bindable(defaultValue = "Ford") (2)
@NotBlank (3)
String getManufacturer();
@Min(1L)
int getCylinders();
@NotNull
CrankShaft getCrankShaft(); (4)
@ConfigurationProperties("crank-shaft")
interface CrankShaft { (5)
Optional<Double> getRodLength(); (6)
}
}
@ConfigurationProperties Example
import io.micronaut.context.annotation.ConfigurationProperties
import io.micronaut.core.bind.annotation.Bindable
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
@ConfigurationProperties("my.engine") (1)
interface EngineConfig {
@Bindable(defaultValue = "Ford") (2)
@NotBlank (3)
String getManufacturer()
@Min(1L)
int getCylinders()
@NotNull
CrankShaft getCrankShaft() (4)
@ConfigurationProperties("crank-shaft")
static interface CrankShaft { (5)
Optional<Double> getRodLength() (6)
}
}
@ConfigurationProperties Example
import io.micronaut.context.annotation.ConfigurationProperties
import io.micronaut.core.bind.annotation.Bindable
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
@ConfigurationProperties("my.engine") (1)
interface EngineConfig {
@get:Bindable(defaultValue = "Ford") (2)
@get:NotBlank (3)
val manufacturer: String
@get:Min(1L)
val cylinders: Int
@get:NotNull
val crankShaft: CrankShaft (4)
@ConfigurationProperties("crank-shaft")
interface CrankShaft { (5)
val rodLength: Double? (6)
}
}
1 | The @ConfigurationProperties annotation takes the configuration prefix and is declared on an interface |
2 | You can use @Bindable to set a default value |
3 | Validation annotations can also be used |
4 | You can also specify references to other @ConfigurationProperties beans. |
5 | You can nest immutable configuration |
6 | Optional configuration can be indicated by returning an Optional or specifying @Nullable |
In this case Micronaut provides a compile-time implementation that delegates all getters to call the getProperty(..)
method of the Environment interface.
This has the advantage that if the application configuration is refreshed (for example by invoking the /refresh
endpoint), the injected interface automatically sees the new values.
If you try to specify any other abstract method other than a getter, a compilation error occurs (default methods are supported). |
Another way to implement immutable configuration is to define a class and use the @ConfigurationInject annotation on a constructor of a @ConfigurationProperties or @EachProperty bean.
For example:
@ConfigurationProperties Example
import io.micronaut.core.bind.annotation.Bindable;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.context.annotation.ConfigurationInject;
import io.micronaut.context.annotation.ConfigurationProperties;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Optional;
@ConfigurationProperties("my.engine") (1)
public class EngineConfig {
private final String manufacturer;
private final int cylinders;
private final CrankShaft crankShaft;
@ConfigurationInject (2)
public EngineConfig(
@Bindable(defaultValue = "Ford") @NotBlank String manufacturer, (3)
@Min(1L) int cylinders, (4)
@NotNull CrankShaft crankShaft) {
this.manufacturer = manufacturer;
this.cylinders = cylinders;
this.crankShaft = crankShaft;
}
public String getManufacturer() {
return manufacturer;
}
public int getCylinders() {
return cylinders;
}
public CrankShaft getCrankShaft() {
return crankShaft;
}
@ConfigurationProperties("crank-shaft")
public static class CrankShaft { (5)
private final Double rodLength; (6)
@ConfigurationInject
public CrankShaft(@Nullable Double rodLength) {
this.rodLength = rodLength;
}
public Optional<Double> getRodLength() {
return Optional.ofNullable(rodLength);
}
}
}
@ConfigurationProperties Example
import io.micronaut.context.annotation.ConfigurationInject
import io.micronaut.context.annotation.ConfigurationProperties
import io.micronaut.core.bind.annotation.Bindable
import javax.annotation.Nullable
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
@ConfigurationProperties("my.engine") (1)
class EngineConfig {
final String manufacturer
final int cylinders
final CrankShaft crankShaft
@ConfigurationInject (2)
EngineConfig(
@Bindable(defaultValue = "Ford") @NotBlank String manufacturer, (3)
@Min(1L) int cylinders, (4)
@NotNull CrankShaft crankShaft) {
this.manufacturer = manufacturer
this.cylinders = cylinders
this.crankShaft = crankShaft
}
@ConfigurationProperties("crank-shaft")
static class CrankShaft { (5)
private final Double rodLength (6)
@ConfigurationInject
CrankShaft(@Nullable Double rodLength) {
this.rodLength = rodLength
}
Optional<Double> getRodLength() {
Optional.ofNullable(rodLength)
}
}
}
@ConfigurationProperties Example
import io.micronaut.context.annotation.ConfigurationInject
import io.micronaut.context.annotation.ConfigurationProperties
import io.micronaut.core.bind.annotation.Bindable
import java.util.Optional
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
@ConfigurationProperties("my.engine") (1)
data class EngineConfig @ConfigurationInject (2)
constructor(
@Bindable(defaultValue = "Ford") @NotBlank val manufacturer: String, (3)
@Min(1) val cylinders: Int, (4)
@NotNull val crankShaft: CrankShaft) {
@ConfigurationProperties("crank-shaft")
data class CrankShaft @ConfigurationInject
constructor((5)
private val rodLength: Double? (6)
) {
fun getRodLength(): Optional<Double> {
return Optional.ofNullable(rodLength)
}
}
}
1 | The @ConfigurationProperties annotation takes the configuration prefix |
2 | The @ConfigurationInject annotation is defined on the constructor |
3 | You can use @Bindable to set a default value |
4 | Validation annotations can be used too |
5 | You can nest immutable configuration |
6 | Optional configuration can be indicated with @Nullable |
The @ConfigurationInject annotation provides a hint to Micronaut to prioritize binding values from configuration instead of injecting beans.
Using this approach, to make the configuration refreshable, add the @Refreshable annotation to the class as well. This allows the bean to be re-created in the case of a runtime configuration refresh event. |
There are a few exceptions to this rule. Micronaut will not perform configuration binding for a parameter if any of these conditions is met:
The parameter is annotated with
@Value
(explicit binding)The parameter is annotated with
@Property
(explicit binding)The parameter is annotated with
@Parameter
(parameterized bean handling)The parameter is annotated with
@Inject
(generic bean injection)The type of the parameter is annotated with a bean scope (such as
@Singleton
)
Once you have prepared a type-safe configuration it can be injected into your beans like any other bean:
1 | Inject the EngineConfig bean |
2 | Use the configuration properties |
Configuration values can then be supplied when running the application. For example:
Supply Configuration
ApplicationContext applicationContext = ApplicationContext.run(CollectionUtils.mapOf(
"my.engine.cylinders", "8",
"my.engine.crank-shaft.rod-length", "7.0"
));
Vehicle vehicle = applicationContext.getBean(Vehicle.class);
System.out.println(vehicle.start());
Supply Configuration
ApplicationContext applicationContext = ApplicationContext.run(
"my.engine.cylinders": "8",
"my.engine.crank-shaft.rod-length": "7.0"
)
Vehicle vehicle = applicationContext.getBean(Vehicle)
System.out.println(vehicle.start())
Supply Configuration
val map = mapOf(
"my.engine.cylinders" to "8",
"my.engine.crank-shaft.rod-length" to "7.0"
)
val applicationContext = ApplicationContext.run(map)
val vehicle = applicationContext.getBean(Vehicle::class.java)
println(vehicle.start())
The above example prints: "Ford Engine Starting V8 [rodLength=7B.0]"