Search code examples
javaspringspring-bootconfigurationproperties

Use @DefaultValue annotation with Map type for Spring Configuration Properties class


I am creating @ConfigurationProperties classes for use in a Spring Boot application. We want to have immutable classes so we have no setters and we are using the @ConstructorBinding annotation. Within the constructor we are using the @DefaultValue annotation to set default property values. With simple property types this works fine. However, if the property is a Map I cannot find any way to use this annotation to set the default value. Essentially, I want to know how to put in below:

import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.bind.DefaultValue;

import java.util.Map;

@ConfigurationProperties(prefix = "props")
@ConstructorBinding
@Getter
public class MyProperties {

    private final Map<String, String> mapProperty;

    public MyProperties(@DefaultValue(<PLACEHOLDER>) Map<String, String> mapProperty) {
        this.mapProperty = mapProperty;
    }
}

I have tried using syntax similar to what would be required to populate a list, e.g. @DefaultValue(value = {"a=b", "c=d"}) or @DefaultValue(value = {"a:b", "c:d"}). I have also tried using the Spring Expression Language for inline maps, e.g. @DefaultValue("{a:'b'}"). None of these approaches seem to work, Spring fails to bind the value to the properties object and the object is not created.

Is it not possible to use this annotation to set the default value for a Map?

I know that I could perform a null check in the constructor and assign a default map. I don't want to use that approach because we are using the generated metadata for documentation.


Solution

  • you need define StringToMapConverter

    @Component
    @ConfigurationPropertiesBinding
    public class StringToMapConverter implements Converter<String, Map<String,String>>{
        private final ObjectMapper objectMapper = new ObjectMapper();
        @Override
        public Map<String, String> convert(String source) {
            try {
                return objectMapper.readValue(source,new TypeReference<>(){});
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
    }