4.5 Custom Type Converters

Micronaut includes an extensible type conversion mechanism. To add additional type converters you register beans of type TypeConverter.

The following example shows how to use one of the built-in converters (Map to an Object) or create your own.

Consider the following ConfigurationProperties:

  1. @ConfigurationProperties(MyConfigurationProperties.PREFIX)
  2. public class MyConfigurationProperties {
  3. public static final String PREFIX = "myapp";
  4. protected LocalDate updatedAt;
  5. public LocalDate getUpdatedAt() {
  6. return updatedAt;
  7. }
  8. }
  1. @ConfigurationProperties(MyConfigurationProperties.PREFIX)
  2. class MyConfigurationProperties {
  3. public static final String PREFIX = "myapp"
  4. protected LocalDate updatedAt
  5. LocalDate getUpdatedAt() {
  6. updatedAt
  7. }
  8. }
  1. @ConfigurationProperties(MyConfigurationProperties.PREFIX)
  2. class MyConfigurationProperties {
  3. var updatedAt: LocalDate? = null
  4. protected set
  5. companion object {
  6. const val PREFIX = "myapp"
  7. }
  8. }

The type MyConfigurationProperties has a property named updatedAt of type LocalDate.

To bind this property from a map via configuration:

  1. private static ApplicationContext ctx;
  2. @BeforeClass
  3. public static void setupCtx() {
  4. ctx = ApplicationContext.run(
  5. new LinkedHashMap<String, Object>() {{
  6. put("myapp.updatedAt", (1)
  7. new LinkedHashMap<String, Integer>() {{
  8. put("day", 28);
  9. put("month", 10);
  10. put("year", 1982);
  11. }}
  12. );
  13. }}
  14. );
  15. }
  16. @AfterClass
  17. public static void teardownCtx() {
  18. if(ctx != null) {
  19. ctx.stop();
  20. }
  21. }
  1. @AutoCleanup
  2. @Shared
  3. ApplicationContext ctx = ApplicationContext.run(
  4. "myapp.updatedAt": [day: 28, month: 10, year: 1982] (1)
  5. )
  1. lateinit var ctx: ApplicationContext
  2. @BeforeEach
  3. fun setup() {
  4. ctx = ApplicationContext.run(
  5. mapOf(
  6. "myapp.updatedAt" to mapOf( (1)
  7. "day" to 28,
  8. "month" to 10,
  9. "year" to 1982
  10. )
  11. )
  12. )
  13. }
  14. @AfterEach
  15. fun teardown() {
  16. ctx?.close()
  17. }
1Note how we match the myapp prefix and updatedAt property name in our MyConfigurationProperties class above

This won’t work by default, since there is no built-in conversion from Map to LocalDate. To resolve this, define a custom TypeConverter:

  1. import io.micronaut.core.convert.ConversionContext;
  2. import io.micronaut.core.convert.ConversionService;
  3. import io.micronaut.core.convert.TypeConverter;
  4. import jakarta.inject.Singleton;
  5. import java.time.DateTimeException;
  6. import java.time.LocalDate;
  7. import java.util.Map;
  8. import java.util.Optional;
  9. @Singleton
  10. public class MapToLocalDateConverter implements TypeConverter<Map, LocalDate> { (1)
  11. @Override
  12. public Optional<LocalDate> convert(Map propertyMap, Class<LocalDate> targetType, ConversionContext context) {
  13. Optional<Integer> day = ConversionService.SHARED.convert(propertyMap.get("day"), Integer.class);
  14. Optional<Integer> month = ConversionService.SHARED.convert(propertyMap.get("month"), Integer.class);
  15. Optional<Integer> year = ConversionService.SHARED.convert(propertyMap.get("year"), Integer.class);
  16. if (day.isPresent() && month.isPresent() && year.isPresent()) {
  17. try {
  18. return Optional.of(LocalDate.of(year.get(), month.get(), day.get())); (2)
  19. } catch (DateTimeException e) {
  20. context.reject(propertyMap, e); (3)
  21. return Optional.empty();
  22. }
  23. }
  24. return Optional.empty();
  25. }
  26. }
  1. import io.micronaut.core.convert.ConversionContext
  2. import io.micronaut.core.convert.ConversionService
  3. import io.micronaut.core.convert.TypeConverter
  4. import jakarta.inject.Singleton
  5. import java.time.DateTimeException
  6. import java.time.LocalDate
  7. @Singleton
  8. class MapToLocalDateConverter implements TypeConverter<Map, LocalDate> { (1)
  9. @Override
  10. Optional<LocalDate> convert(Map propertyMap, Class<LocalDate> targetType, ConversionContext context) {
  11. Optional<Integer> day = ConversionService.SHARED.convert(propertyMap.day, Integer)
  12. Optional<Integer> month = ConversionService.SHARED.convert(propertyMap.month, Integer)
  13. Optional<Integer> year = ConversionService.SHARED.convert(propertyMap.year, Integer)
  14. if (day.present && month.present && year.present) {
  15. try {
  16. return Optional.of(LocalDate.of(year.get(), month.get(), day.get())) (2)
  17. } catch (DateTimeException e) {
  18. context.reject(propertyMap, e) (3)
  19. return Optional.empty()
  20. }
  21. }
  22. return Optional.empty()
  23. }
  24. }
  1. import io.micronaut.core.convert.ConversionContext
  2. import io.micronaut.core.convert.ConversionService
  3. import io.micronaut.core.convert.TypeConverter
  4. import java.time.DateTimeException
  5. import java.time.LocalDate
  6. import java.util.Optional
  7. import jakarta.inject.Singleton
  8. @Singleton
  9. class MapToLocalDateConverter : TypeConverter<Map<*, *>, LocalDate> { (1)
  10. override fun convert(propertyMap: Map<*, *>, targetType: Class<LocalDate>, context: ConversionContext): Optional<LocalDate> {
  11. val day = ConversionService.SHARED.convert(propertyMap["day"], Int::class.java)
  12. val month = ConversionService.SHARED.convert(propertyMap["month"], Int::class.java)
  13. val year = ConversionService.SHARED.convert(propertyMap["year"], Int::class.java)
  14. if (day.isPresent && month.isPresent && year.isPresent) {
  15. try {
  16. return Optional.of(LocalDate.of(year.get(), month.get(), day.get())) (2)
  17. } catch (e: DateTimeException) {
  18. context.reject(propertyMap, e) (3)
  19. return Optional.empty()
  20. }
  21. }
  22. return Optional.empty()
  23. }
  24. }
1The class implements TypeConverter which has two generic arguments, the type you are converting from, and the type you are converting to
2The implementation delegates to the default shared conversion service to convert the values from the Map used to create a LocalDate
3If an exception occurs during binding, call reject(..) which propagates additional information to the container