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

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

@ConfigurationProperties Example

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

@ConfigurationProperties Example

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

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

@ConfigurationProperties Example

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

@ConfigurationProperties Example

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

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)
  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)
  7. println(vehicle.start())

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