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 that is annotated with @ConfigurationProperties. For example:
@ConfigurationProperties Example
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.bind.annotation.Bindable;
import javax.validation.constraints.*;
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.*
@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.*
import java.util.Optional
@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 if you want |
3 | Validation annotations can be used too |
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 what Micronaut does is provide a compilation time implementation that delegates all getters to call the getProperty(..)
method of the Environment interface.
This has the advantage that if the application’s configuration is refreshed (for example by invoking the /refresh
endpoint) then the injected interface will automatically see the new values.
If you try to specify any other abstract method other than a getter a compilation error will occur (default methods are supported). |
An alternative way to implement immutable configuration is to define a class and use the @ConfigurationInject annotation on a constructor of a @ConfigurationProperties or @EachProperty bean.
An example of an immutable configuration class can be seen below:
@ConfigurationProperties Example
import io.micronaut.context.annotation.*;
import io.micronaut.core.bind.annotation.Bindable;
import edu.umd.cs.findbugs.annotations.Nullable;
import javax.validation.constraints.*;
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.*
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 {
private final String manufacturer
private final int cylinders
private 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
}
String getManufacturer() {
return manufacturer
}
int getCylinders() {
return cylinders
}
CrankShaft getCrankShaft() {
return crankShaft
}
@ConfigurationProperties("crank-shaft")
static class CrankShaft { (5)
private final Double rodLength (6)
@ConfigurationInject
CrankShaft(@Nullable Double rodLength) {
this.rodLength = rodLength
}
Optional<Double> getRodLength() {
return Optional.ofNullable(rodLength)
}
}
}
@ConfigurationProperties Example
import io.micronaut.context.annotation.*
import io.micronaut.core.bind.annotation.Bindable
import javax.validation.constraints.*
import java.util.Optional
@ConfigurationProperties("my.engine") (1)
data class EngineConfig @ConfigurationInject (2)
constructor(
@Bindable(defaultValue = "Ford") @NotBlank (3)
val manufacturer: String,
@Min(1L) (4)
val cylinders: Int,
@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 if you want |
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 if you want the configuration to be refreshable then you must 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 one 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 type of the parameter is annotated with a bean scope (such as
@Singleton
)
Once you have prepared a type safe configuration it can simply be injected into your objects 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.class)
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)
The above example prints: "Ford Engine Starting V8 [rodLength=7B.0]"