How to inject Maps using Spring @Value

5/15/2018

We are using Spring Boot 2.

We want to use the refresh mechanism of Spring Boot, but due to a bug we can't use @Configuration, hence we are forced to replace all theses by @Value in combination with @RefreshScope.

So we used that:

@Configuration
@RefreshScope
public class MyConfig {
     @Value("${myMap}")
    private Map<String, String> myMap;
}

With that YAML file for example:

myMaps:
  key1: Value
  key2: Another Value

But we get an error out of it:

Error creating bean with name 'scopedTarget.myMap': Unsatisfied dependency expressed through field 'mapMap'; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'java.util.Map'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Map': no matching editors or conversion strategy found

We found already other thread with a similar topic:

But we can't use other kind of property bindings, as we are deploying this app into kubernetes and use the config maps of kubernetes.

So we wonder, if there is any other chance to get @Value working together with Map

-- Michael D.
java
kubernetes
spring
spring-boot

2 Answers

5/15/2018

I don't think Kubernetes should prevent you from using @ConfigurationProperties (example here) with @RefreshScope. (Or is something else blocking you from using this?)

You could set up a configmap containing your properties file and mount that as a volume.

How you trigger a refresh is another question (if you need hot loading instead of changing your configmap together with your app). One option is to use spring-cloud-kubernetes which uses a similar approach and has multiple reload-triggering options. I'd expect you could use @ConfigurationProperties just like in the example for that project. Alternatively, it's also possible to watch for changes but that would require introducing more code.

-- Ryan Dawson
Source: StackOverflow

5/16/2018

I found something, that might solve my problem, but perhaps there is a better solution than this:

Based on org.springframework.cloud.logging.LoggingRebinder.setLogLevels(LoggingSystem, Environment)

private static final Bindable<Map<String, String>> STRING_STRING_MAP = Bindable
        .mapOf(String.class, String.class);

protected void setLogLevels(LoggingSystem system, Environment environment) {
    Map<String, String> levels = Binder.get(environment)
            .bind("logging.level", STRING_STRING_MAP).orElseGet(Collections::emptyMap);
    for (Entry<String, String> entry : levels.entrySet()) {
        setLogLevel(system, environment, entry.getKey(), entry.getValue().toString());
    }
}
-- Michael D.
Source: StackOverflow