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

  1. import io.micronaut.context.annotation.ConfigurationProperties;
  2. import io.micronaut.core.bind.annotation.Bindable;
  3. import javax.validation.constraints.*;
  4. import java.util.Optional;
  5. @ConfigurationProperties("my.engine") (1)
  6. public interface EngineConfig {
  7. @Bindable(defaultValue = "Ford") (2)
  8. @NotBlank (3)
  9. String getManufacturer();
  10. @Min(1L)
  11. int getCylinders();
  12. @NotNull
  13. CrankShaft getCrankShaft(); (4)
  14. @ConfigurationProperties("crank-shaft")
  15. interface CrankShaft { (5)
  16. Optional<Double> getRodLength(); (6)
  17. }
  18. }

@ConfigurationProperties Example

  1. import io.micronaut.context.annotation.ConfigurationProperties
  2. import io.micronaut.core.bind.annotation.Bindable
  3. import javax.validation.constraints.*
  4. @ConfigurationProperties("my.engine") (1)
  5. interface EngineConfig {
  6. @Bindable(defaultValue = "Ford") (2)
  7. @NotBlank (3)
  8. String getManufacturer()
  9. @Min(1L)
  10. int getCylinders()
  11. @NotNull
  12. CrankShaft getCrankShaft() (4)
  13. @ConfigurationProperties("crank-shaft")
  14. static interface CrankShaft { (5)
  15. Optional<Double> getRodLength() (6)
  16. }
  17. }

@ConfigurationProperties Example

  1. import io.micronaut.context.annotation.ConfigurationProperties
  2. import io.micronaut.core.bind.annotation.Bindable
  3. import javax.validation.constraints.*
  4. import java.util.Optional
  5. @ConfigurationProperties("my.engine") (1)
  6. interface EngineConfig {
  7. @get:Bindable(defaultValue = "Ford") (2)
  8. @get:NotBlank (3)
  9. val manufacturer: String
  10. @get:Min(1L)
  11. val cylinders: Int
  12. @get:NotNull
  13. val crankShaft: CrankShaft (4)
  14. @ConfigurationProperties("crank-shaft")
  15. interface CrankShaft { (5)
  16. val rodLength: Double? (6)
  17. }
  18. }
1The @ConfigurationProperties annotation takes the configuration prefix and is declared on an interface
2You can use @Bindable to set a default value if you want
3Validation annotations can be used too
4You can also specify references to other @ConfigurationProperties beans.
5You can nest immutable configuration
6Optional 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

  1. import io.micronaut.context.annotation.*;
  2. import io.micronaut.core.bind.annotation.Bindable;
  3. import javax.annotation.Nullable;
  4. import javax.validation.constraints.*;
  5. import java.util.Optional;
  6. @ConfigurationProperties("my.engine") (1)
  7. public class EngineConfig {
  8. private final String manufacturer;
  9. private final int cylinders;
  10. private final CrankShaft crankShaft;
  11. @ConfigurationInject (2)
  12. public EngineConfig(
  13. @Bindable(defaultValue = "Ford") @NotBlank String manufacturer, (3)
  14. @Min(1L) int cylinders, (4)
  15. @NotNull CrankShaft crankShaft) {
  16. this.manufacturer = manufacturer;
  17. this.cylinders = cylinders;
  18. this.crankShaft = crankShaft;
  19. }
  20. public String getManufacturer() {
  21. return manufacturer;
  22. }
  23. public int getCylinders() {
  24. return cylinders;
  25. }
  26. public CrankShaft getCrankShaft() {
  27. return crankShaft;
  28. }
  29. @ConfigurationProperties("crank-shaft")
  30. public static class CrankShaft { (5)
  31. private final Double rodLength; (6)
  32. @ConfigurationInject
  33. public CrankShaft(@Nullable Double rodLength) {
  34. this.rodLength = rodLength;
  35. }
  36. public Optional<Double> getRodLength() {
  37. return Optional.ofNullable(rodLength);
  38. }
  39. }
  40. }

@ConfigurationProperties Example

  1. import io.micronaut.context.annotation.*
  2. import io.micronaut.core.bind.annotation.Bindable
  3. import javax.annotation.Nullable
  4. import javax.validation.constraints.Min
  5. import javax.validation.constraints.NotBlank
  6. import javax.validation.constraints.NotNull
  7. @ConfigurationProperties("my.engine") (1)
  8. class EngineConfig {
  9. private final String manufacturer
  10. private final int cylinders
  11. private final CrankShaft crankShaft
  12. @ConfigurationInject (2)
  13. EngineConfig(
  14. @Bindable(defaultValue = "Ford") @NotBlank String manufacturer, (3)
  15. @Min(1L) int cylinders, (4)
  16. @NotNull CrankShaft crankShaft) {
  17. this.manufacturer = manufacturer
  18. this.cylinders = cylinders
  19. this.crankShaft = crankShaft
  20. }
  21. String getManufacturer() {
  22. return manufacturer
  23. }
  24. int getCylinders() {
  25. return cylinders
  26. }
  27. CrankShaft getCrankShaft() {
  28. return crankShaft
  29. }
  30. @ConfigurationProperties("crank-shaft")
  31. static class CrankShaft { (5)
  32. private final Double rodLength (6)
  33. @ConfigurationInject
  34. CrankShaft(@Nullable Double rodLength) {
  35. this.rodLength = rodLength
  36. }
  37. Optional<Double> getRodLength() {
  38. return Optional.ofNullable(rodLength)
  39. }
  40. }
  41. }

@ConfigurationProperties Example

  1. import io.micronaut.context.annotation.*
  2. import io.micronaut.core.bind.annotation.Bindable
  3. import javax.validation.constraints.*
  4. import java.util.Optional
  5. @ConfigurationProperties("my.engine") (1)
  6. data class EngineConfig @ConfigurationInject (2)
  7. constructor(
  8. @Bindable(defaultValue = "Ford") @NotBlank (3)
  9. val manufacturer: String,
  10. @Min(1L) (4)
  11. val cylinders: Int,
  12. @NotNull val crankShaft: CrankShaft) {
  13. @ConfigurationProperties("crank-shaft")
  14. data class CrankShaft @ConfigurationInject
  15. constructor((5)
  16. private val rodLength: Double? (6)
  17. ) {
  18. fun getRodLength(): Optional<Double> {
  19. return Optional.ofNullable(rodLength)
  20. }
  21. }
  22. }
1The @ConfigurationProperties annotation takes the configuration prefix
2The @ConfigurationInject annotation is defined on the constructor
3You can use @Bindable to set a default value if you want
4Validation annotations can be used too
5You can nest immutable configuration
6Optional 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:

1Inject the EngineConfig bean
2Use the configuration properties

Configuration values can then be supplied when running the application. For example:

Supply Configuration

  1. ApplicationContext applicationContext = ApplicationContext.run(CollectionUtils.mapOf(
  2. "my.engine.cylinders", "8",
  3. "my.engine.crank-shaft.rod-length", "7.0"
  4. ));
  5. Vehicle vehicle = applicationContext.getBean(Vehicle.class);
  6. System.out.println(vehicle.start());

Supply Configuration

  1. ApplicationContext applicationContext = ApplicationContext.run(
  2. "my.engine.cylinders": "8",
  3. "my.engine.crank-shaft.rod-length": "7.0"
  4. )
  5. Vehicle vehicle = applicationContext.getBean(Vehicle.class)
  6. System.out.println(vehicle.start())

Supply Configuration

  1. val map = mapOf(
  2. "my.engine.cylinders" to "8",
  3. "my.engine.crank-shaft.rod-length" to "7.0"
  4. )
  5. val applicationContext = ApplicationContext.run(map)
  6. val vehicle = applicationContext.getBean(Vehicle::class.java)

The above example prints: "Ford Engine Starting V8 [rodLength=7B.0]"