91

How can I inject values into a Map from the properties file using the @Value annotation in Spring?

My Spring Java class is and I tried using the $, but got the following error message:

Could not autowire field: private java.util.Map Test.standard; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'com.test.standard' in string value "${com.test.standard}"

@ConfigurationProperty("com.hello.foo")
public class Test {

   @Value("${com.test.standard}")
   private Map<String,Pattern> standard = new LinkedHashMap<String,Pattern>

   private String enabled;

}

I have the following properties in a .properties file

com.test.standard.name1=Pattern1
com.test.standard.name2=Pattern2
com.test.standard.name3=Pattern3
com.hello.foo.enabled=true
Mark Rotteveel
  • 90,369
  • 161
  • 124
  • 175
yathirigan
  • 5,027
  • 16
  • 61
  • 101
  • You will need to use spring expression language. A similar question which used list (https://stackoverflow.com/questions/27390363/spring-how-to-inject-an-inline-list-of-strings-using-value-annotation). I am not sure you can do what you want out of the box. This question https://stackoverflow.com/questions/28369458/how-to-fill-hashmap-from-java-property-file-with-spring-value is a bit more to your point. Uses a custom property mapper – Laurentiu L. Jun 07 '15 at 09:46
  • What exactly do you want in your map? It seems you also expect some type converstion to a `Pattern`? What kind of `Pattern` class is that? – K Erlandsson Jun 07 '15 at 09:48
  • @Erlandsson this is a RegEx pattern, we will define valid regex pattern strings in the value – yathirigan Jun 07 '15 at 17:34
  • 1
    @LaurentuiL uin Spring boot, i am able to directly inject a map if the map matches the prefix described at class level but,my problem is the prefix at class level and this attribute level is different – yathirigan Jun 07 '15 at 17:51

8 Answers8

167

You can inject values into a Map from the properties file using the @Value annotation like this.

The property in the properties file.

propertyname={key1:'value1',key2:'value2',....}

In your code.

@Value("#{${propertyname}}")  private Map<String,String> propertyname;

Note the hashtag as part of the annotation.

Stijn Van Bael
  • 4,792
  • 2
  • 29
  • 38
Michael Rahenkamp
  • 1,671
  • 2
  • 9
  • 2
  • 1
    values must be quotes, else I get a `SpelEvaluationException` – dustin.schultz May 01 '16 at 14:25
  • 12
    How to set default value if the property is absent to prevent exception occurred? – petertc Sep 08 '16 at 01:34
  • If you want to use strings with whitespaces ( e.g _USB Camera 0xfd120000046d0819_ ) as keys, use single quotes as for values. – partTimeNinja Sep 11 '16 at 14:09
  • what if there's a `:` in the key? – xenoterracide Dec 14 '16 at 18:21
  • 3
    Also seems to do type conversions e.g.: `@Value("#{${double.map}}") final Map doubleMap` – PeterK Sep 21 '17 at 15:56
  • How to set default value ? – emanuel07 Jun 03 '18 at 09:47
  • 6
    How to specify the same in a yml file – Mukul Anand Jun 25 '18 at 13:18
  • 4
    @MukulAnand in yaml it looks like this `propertyname : > { key1:'value', key2:'value' }` Sorry, I'm not able to format the linebreaks correct – joemat Sep 28 '18 at 06:55
  • 1
    _"Note the hashtag as part of the annotation..."_ – Mike D3ViD Tyson Nov 06 '18 at 12:00
  • Is it actually possible to do this? I'm trying to use SPeL in code to do the same thing, but it just says "No converter found capable of converting from type [java.lang.String] to type [java.util.Map]". – David M. Karr Apr 03 '19 at 13:39
  • 5
    pattern "propertyname: {key1:'value1',key2:'value2',....}" does not work when injecting map from .yaml : java.lang.IllegalArgumentException: Could not resolve placeholder – Andrey M. Stepanov Apr 15 '19 at 21:47
  • What if value has SpEL expression with `'`? :( – Woland Nov 15 '19 at 13:08
  • 2
    **Be aware**, this isn't the same as using `propertyname.key1=...` and `propertyname.key2=...`. In stead, it uses a single property called `propertyname`. The equivalent in **YAML** is to **treat it as a string** as well and by **wrapping it in quotes**, such as: `"{key1:'value1',key2:'value2',....}"`. For more infor, check [this question](https://stackoverflow.com/q/59210260/1915448). – g00glen00b Dec 06 '19 at 10:31
  • @MikeD3ViDTyson - Could you please guide me here: https://stackoverflow.com/questions/60899860/spring-spel-expression-language-to-create-map-of-string-and-custom-object ? – PAA Mar 28 '20 at 10:25
  • 1
    Thank you for answering the actual question that was asked!!! The chosen answer may have been what the OP wanted to know. But, this answer actually answered the question THAT WAS ASKED!! Huge help!!! Thanks! – Brunke Jun 19 '20 at 18:19
  • you can set the default value like this for an empty map: @Value("#{${tomcat.oleaconfig:{:}}}") – Francois Marot Oct 01 '20 at 09:01
  • how about Map>? Is this possible? – Artanis Zeratul Mar 20 '21 at 02:26
22

I believe Spring Boot supports loading properties maps out of the box with @ConfigurationProperties annotation.

According that docs you can load properties:

my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com

into bean like this:

@ConfigurationProperties(prefix="my")
public class Config {

    private List<String> servers = new ArrayList<String>();

    public List<String> getServers() {
        return this.servers;
    }
}

I used @ConfigurationProperties feature before, but without loading into map. You need to use @EnableConfigurationProperties annotation to enable this feature.

Cool stuff about this feature is that you can validate your properties.

luboskrnac
  • 22,667
  • 9
  • 77
  • 88
  • yes but, my problem is.. The Test class has its own @ConfigurationProperties prefix. So i want to use a diff prefix for this member variable alone . how can i do that ? – yathirigan Jun 07 '15 at 17:37
  • 1
    hmm, I missed that. So I would create separate two beans with @ConfiguraitonProperties annotation and autowire them into test class. – luboskrnac Jun 07 '15 at 17:53
  • may have worked for the OP, but question didn't specify boot, and this problem doesn't work for general Spring, without boot. – xenoterracide Dec 13 '16 at 22:40
  • 10
    the question is how to inject to a map using @Value annotation, but you are telling all sort of things rather than giving the answer to the question. Alternatives are okay but please stick to the answer as well – Mukul Anand Jun 25 '18 at 12:33
19

You can inject .properties as a map in your class using @Resource annotation.

If you are working with XML based configuration, then add below bean in your spring configuration file:

 <bean id="myProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
      <property name="location" value="classpath:your.properties"/>
 </bean>

For, Annotation based:

@Bean(name = "myProperties")
public static PropertiesFactoryBean mapper() {
        PropertiesFactoryBean bean = new PropertiesFactoryBean();
        bean.setLocation(new ClassPathResource(
                "your.properties"));
        return bean;
}

Then you can pick them up in your application as a Map:

@Resource(name = "myProperties")
private Map<String, String> myProperties;
Arpit Aggarwal
  • 24,784
  • 14
  • 83
  • 102
  • We have used Spring Cloud Config server to provide the configurations, hence class path approach might not work. And we don't use XMLs – yathirigan Jun 07 '15 at 17:39
  • @Arpit - Could you please guide me here : https://stackoverflow.com/questions/60899860/spring-spel-expression-language-to-create-map-of-string-and-custom-object? – PAA Mar 28 '20 at 10:40
17

I had a simple code for Spring Cloud Config

like this:

In application.properties

spring.data.mongodb.db1=mongodb://test@test1.com

spring.data.mongodb.db2=mongodb://test@test2.com

read

@Bean(name = "mongoConfig")
@ConfigurationProperties(prefix = "spring.data.mongodb")
public Map<String, Map<String, String>> mongoConfig() {
    return new HashMap();
}

use

@Autowired
@Qualifier(value = "mongoConfig")
private Map<String, String> mongoConfig;

@Bean(name = "mongoTemplates")
public HashMap<String, MongoTemplate> mongoTemplateMap() throws UnknownHostException {
    HashMap<String, MongoTemplate> mongoTemplates = new HashMap<>();
    for (Map.Entry<String, String>> entry : mongoConfig.entrySet()) {
        String k = entry.getKey();
        String v = entry.getValue();
        MongoTemplate template = new MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI(v)));
        mongoTemplates.put(k, template);
    }
    return mongoTemplates;
}
Community
  • 1
  • 1
liuxun
  • 201
  • 2
  • 4
  • 1
    I think your definition for the bean mongoConfig is incorrect. The method should be defined like this. public Map mongoConfig() { return new HashMap(); } – rslj Jul 05 '19 at 14:19
  • this is probably the most elegant way if you simply want to inject a map from application.yml – Agoston Horvath Jan 16 '20 at 14:55
12

To get this working with YAML, do this:

property-name: '{
  key1: "value1",
  key2: "value2"
}'
wild_nothing
  • 2,612
  • 1
  • 33
  • 45
9

Following worked for me:

SpingBoot 2.1.7.RELEASE

YAML Property (Notice value sourrounded by single quotes)

property:
   name: '{"key1": false, "key2": false, "key3": true}'

In Java/Kotlin annotate field with (Notice use of #) (For java no need to escape '$' with '\')

@Value("#{\${property.name}}")
7

Here is how we did it. Two sample classes as follow:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
@EnableKafka
@Configuration
@EnableConfigurationProperties(KafkaConsumerProperties.class)
public class KafkaContainerConfig {

    @Autowired
    protected KafkaConsumerProperties kafkaConsumerProperties;

    @Bean
    public ConsumerFactory<String, String> consumerFactory() {
        return new DefaultKafkaConsumerFactory<>(kafkaConsumerProperties.getKafkaConsumerConfig());
    }
...

@Configuration
@ConfigurationProperties
public class KafkaConsumerProperties {
    protected Map<String, Object> kafkaConsumerConfig = new HashMap<>();

    @ConfigurationProperties("kafkaConsumerConfig")
    public Map<String, Object> getKafkaConsumerConfig() {
        return (kafkaConsumerConfig);
    }
...

To provide the kafkaConsumer config from a properties file, you can use: mapname[key]=value

//application.properties
kafkaConsumerConfig[bootstrap.servers]=localhost:9092, localhost:9093, localhost:9094
kafkaConsumerConfig[group.id]=test-consumer-group-local
kafkaConsumerConfig[value.deserializer]=org.apache.kafka.common.serialization.StringDeserializer
kafkaConsumerConfig[key.deserializer=org.apache.kafka.common.serialization.StringDeserializer

To provide the kafkaConsumer config from a yaml file, you can use "[key]": value In application.yml file:

kafkaConsumerConfig:
  "[bootstrap.servers]": localhost:9092, localhost:9093, localhost:9094
  "[group.id]": test-consumer-group-local
  "[value.deserializer]": org.apache.kafka.common.serialization.StringDeserializer
  "[key.deserializer]": org.apache.kafka.common.serialization.StringDeserializer
Neil Han
  • 420
  • 5
  • 8
0

You can use below code.

Below code for application.yml

my:
  mapValues:
    dbData: '{
      "connectionURL": "http://tesst:3306",
        "userName": "myUser",
        "password": "password123"
    }'

to access these key and values using @Value annotation use below java code.

@Value("#{${my.mapValues.dbData}}")
private Map<String,String> dbValues;
Harsh
  • 2,633
  • 26
  • 16