How to fill HashMap from java property file with Spring @Value
Asked Answered
K

8

71

Is it possible to use Spring @Value, to map values from properties file to the HashMap.

Currently I have something like this, and mapping one value is not a problem. But I need to map custom values in HashMap expirations. Is something like this possible?

@Service
@PropertySource(value = "classpath:my_service.properties")
public class SomeServiceImpl implements SomeService {


    @Value("#{conf['service.cache']}")
    private final boolean useCache = false;

    @Value("#{conf['service.expiration.[<custom name>]']}")
    private final HashMap<String, String> expirations = new HashMap<String, String>();

Property file: 'my_service.properties'

service.cache=true
service.expiration.name1=100
service.expiration.name2=20

Is it posible to map like this key:value set

  • name1 = 100

  • name2 = 20

Kalindi answered 6/2, 2015 at 15:45 Comment(2)
new and Spring bean factory are orthogonal. new means "no Spring"Okhotsk
@Okhotsk cant be generalized like that. new Entity, new ValueObject does not come under thisJoplin
K
15

I make one solution inspired by the previous post.

Register property file in the Spring configuration:

<util:properties id="myProp" location="classpath:my.properties"/>

And I create component:

@Component("PropertyMapper")
public class PropertyMapper {

    @Autowired
    ApplicationContext applicationContext;

    public HashMap<String, Object> startWith(String qualifier, String startWith) {
        return startWith(qualifier, startWith, false);
    }

    public HashMap<String, Object> startWith(String qualifier, String startWith, boolean removeStartWith) {
        HashMap<String, Object> result = new HashMap<String, Object>();

        Object obj = applicationContext.getBean(qualifier);
        if (obj instanceof Properties) {
            Properties mobileProperties = (Properties)obj;

            if (mobileProperties != null) {
                for (Entry<Object, Object> e : mobileProperties.entrySet()) {
                    Object oKey = e.getKey();
                    if (oKey instanceof String) {
                        String key = (String)oKey;
                        if (((String) oKey).startsWith(startWith)) {
                            if (removeStartWith) 
                                key = key.substring(startWith.length());
                            result.put(key, e.getValue());
                        }
                    }
                }
            }
        }

        return result;
    }
}

And when I want to map all properties that begin with specifix value to HashMap, with @Value annotation:

@Service
public class MyServiceImpl implements MyService {

    @Value("#{PropertyMapper.startWith('myProp', 'service.expiration.', true)}")
    private HashMap<String, Object> portalExpirations;
Kalindi answered 9/2, 2015 at 15:47 Comment(0)
C
70

You can use the SPEL json-like syntax to write a simple map or a map of list in property file.

simple.map={'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}

map.of.list={\
  'KEY1': {'value1','value2'}, \
  'KEY2': {'value3','value4'}, \
  'KEY3': {'value5'} \
 }

I used \ for multiline property to enhance readability

Then, in Java, you can access and parse it automatically with @Value like this.

@Value("#{${simple.map}}")
Map<String, String> simpleMap;

@Value("#{${map.of.list}}")
Map<String, List<String>> mapOfList;

Here with ${simple.map}, @Value gets the following String from the property file:

"{'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}"

Then, it is evaluated as if it was inlined

@Value("#{{'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}}")

You can learn more in the official documentation

Cephalad answered 21/9, 2018 at 8:34 Comment(4)
What's the syntax to provide an empty map in property file? I can provide a null map with simple.map=#null but simple.map={} generates an exception.Diehard
Maybe you can provide a default value in the code and give no value in the property file.Cephalad
This solution works great, but not in my case. In my properties I need colon: configurationFields: {"DEPARTMENT:": DEPARTMENT-ID, "AGENT:": AGENT-ID}. I tried with double quotes, single quotes, escape character ("DEPARTMENT\:") and nothing is working. Does anyone have some suggestion what I could use?Florida
@Value("#{${simple.map: {'defaultKey' : 'defaultValue'}}}")Enneahedron
R
22

Is it possible to use Spring @Value, to map values from properties file to the HashMap?

Yes, it is. With a little help of code and Spel.

Firstly, consider this singleton Spring-bean (you should scan it):

@Component("PropertySplitter")
public class PropertySplitter {

    /**
     * Example: one.example.property = KEY1:VALUE1,KEY2:VALUE2
     */
    public Map<String, String> map(String property) {
        return this.map(property, ",");
    }

    /**
     * Example: one.example.property = KEY1:VALUE1.1,VALUE1.2;KEY2:VALUE2.1,VALUE2.2
     */
    public Map<String, List<String>> mapOfList(String property) {
        Map<String, String> map = this.map(property, ";");

        Map<String, List<String>> mapOfList = new HashMap<>();
        for (Entry<String, String> entry : map.entrySet()) {
            mapOfList.put(entry.getKey(), this.list(entry.getValue()));
        }

        return mapOfList;
    }

    /**
     * Example: one.example.property = VALUE1,VALUE2,VALUE3,VALUE4
     */
    public List<String> list(String property) {
        return this.list(property, ",");
    }

    /**
     * Example: one.example.property = VALUE1.1,VALUE1.2;VALUE2.1,VALUE2.2
     */
    public List<List<String>> groupedList(String property) {
        List<String> unGroupedList = this.list(property, ";");

        List<List<String>> groupedList = new ArrayList<>();
        for (String group : unGroupedList) {
            groupedList.add(this.list(group));
        }

        return groupedList;

    }

    private List<String> list(String property, String splitter) {
        return Splitter.on(splitter).omitEmptyStrings().trimResults().splitToList(property);
    }

    private Map<String, String> map(String property, String splitter) {
        return Splitter.on(splitter).omitEmptyStrings().trimResults().withKeyValueSeparator(":").split(property);
    }

}

Note: PropertySplitter class uses Splitter utility from Guava. Please refer to its documentation for further details.

Then, in some bean of yours:

@Component
public class MyBean {

    @Value("#{PropertySplitter.map('${service.expiration}')}")
    Map<String, String> propertyAsMap;

}

And finally, the property:

service.expiration = name1:100,name2:20

It's not exactly what you've asked, because this PropertySplitter works with one single property that is transformed into a Map, but I think you could either switch to this way of specifying properties, or modify the PropertySplitter code so that it matches the more hierarchical way you desire.

Rearmost answered 6/2, 2015 at 16:59 Comment(4)
EL1008E: Property or field 'PropertySplitter' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public? getting this error ? anything that i am missing ?Slab
@NirajSonawane PropertySplitter should be in the classpath and Spring needs to scan it and find it, it's another bean.Rearmost
thanks for the response , I am able to create instance/Bean of PropertySplitter. but while injecting values in map i am getting exceptionSlab
Could you please guide me here: #60900360 ?Benzene
S
22

From Spring 4.1.x ( I can't remember specific version though ), you can do something like

@Value("#{${your.properties.key.name}}")
private Map<String, String> myMap;

where your.properties.key.name in your properties file should be something like

your.properties.key.name={\
    name1 : 100, \
    name2 : 200 \
}

Just make sure that you should create PropertySourcesPlaceholderConfigurer bean to make it work both in your app and if you are writing any unit test code to test your code, otherwise ${...} placeholder for the property value won't work as expected and you'll see some weird SpringEL errors.

Sansbury answered 22/6, 2018 at 0:30 Comment(1)
plus one for the simple and easiest newer way to solve the problemCrepe
G
20

The quickest Spring Boot based solution I can think of follows. In my particular example I am migrating data from one system to another. That is why I need a mapping for a field called priority.

First I've created the properties file (priority-migration.properties) like such:

my.prefix.priority.0:0
my.prefix.priority.10:1
my.prefix.priority.15:2
my.prefix.priority.20:2
another.prefix.foo:bar

and put it on the classpath.

Assuming you want to use the map in a spring managed bean/component, annotate your class with:

@Component
@PropertySource("classpath:/priority-migration.properties")

What you actually want in your map is of course only the key/value pairs which are prefixed with my.prefix, i.e. this part:

{
    0:0
    10:1
    15:2
    20:2
}

To achieve that you need to annotate your component with

@ConfigurationProperties("my.prefix")

and create a getter for the priority infix. The latter proved to be mandatory in my case (although the Sring Doc says it is enough to have a property priority and initialize it with a mutable value)

private final Map<Integer, Integer> priorityMap = new HashMap<>();

public Map<Integer, Integer> getPriority() {
    return priorityMap;
}

In the End

It looks something like this:

@Component
@ConfigurationProperties("my.prefix")
@PropertySource("classpath:/priority-migration.properties")
class PriorityProcessor {

    private final Map<Integer, Integer> priorityMap = new HashMap<>();

    public Map<Integer, Integer> getPriority() {
        return priorityMap;
    }

    public void process() {

        Integer myPriority = priorityMap.get(10)
        // use it here
    }
}
Grower answered 5/9, 2017 at 11:17 Comment(5)
@ConfigurationProperties is a Spring Boot annotation, not a Spring annotationMercator
But is there a way to make this reloadable on runtime? Will the changes be automatically reflected if changes are made to the property file while the application is running?Metropolis
Hi Mayank. The properties won't be reloaded with this example. But you should look into @RefreshScope and put it under @ConfigurationProperties("my.prefix"). Please refer to this article (see 4.1): baeldung.com/spring-reloading-properties . This should work in theory, but I didn't test it myself. Good luck.Grower
Thanks Viktor followed your suggestion finally and we have modified all our services to load configuration using Spring cloud config. With the actuator refresh end point I am able to reload the properties at run time. Also this allows me to have a central GIT location for all my propertiesMetropolis
Thank you for providing this alernative for those using Spring Boot. It is the recommended way since among other reasons, it allows to inject properties as objects. I find it easier to group cohesive properties together. This way we can create modular configuration type safe. docs.spring.io/spring-boot/docs/current/reference/html/…Cephalad
K
15

I make one solution inspired by the previous post.

Register property file in the Spring configuration:

<util:properties id="myProp" location="classpath:my.properties"/>

And I create component:

@Component("PropertyMapper")
public class PropertyMapper {

    @Autowired
    ApplicationContext applicationContext;

    public HashMap<String, Object> startWith(String qualifier, String startWith) {
        return startWith(qualifier, startWith, false);
    }

    public HashMap<String, Object> startWith(String qualifier, String startWith, boolean removeStartWith) {
        HashMap<String, Object> result = new HashMap<String, Object>();

        Object obj = applicationContext.getBean(qualifier);
        if (obj instanceof Properties) {
            Properties mobileProperties = (Properties)obj;

            if (mobileProperties != null) {
                for (Entry<Object, Object> e : mobileProperties.entrySet()) {
                    Object oKey = e.getKey();
                    if (oKey instanceof String) {
                        String key = (String)oKey;
                        if (((String) oKey).startsWith(startWith)) {
                            if (removeStartWith) 
                                key = key.substring(startWith.length());
                            result.put(key, e.getValue());
                        }
                    }
                }
            }
        }

        return result;
    }
}

And when I want to map all properties that begin with specifix value to HashMap, with @Value annotation:

@Service
public class MyServiceImpl implements MyService {

    @Value("#{PropertyMapper.startWith('myProp', 'service.expiration.', true)}")
    private HashMap<String, Object> portalExpirations;
Kalindi answered 9/2, 2015 at 15:47 Comment(0)
H
3

Solution for pulling Map using @Value from application.yml property coded as multiline

application.yml

other-prop: just for demo 

my-map-property-name: "{\
         key1: \"ANY String Value here\", \  
         key2: \"any number of items\" , \ 
         key3: \"Note the Last item does not have comma\" \
         }"

other-prop2: just for demo 2 

Here the value for our map property "my-map-property-name" is stored in JSON format inside a string and we have achived multiline using \ at end of line

myJavaClass.java

import org.springframework.beans.factory.annotation.Value;

public class myJavaClass {

@Value("#{${my-map-property-name}}") 
private Map<String,String> myMap;

public void someRandomMethod (){
    if(myMap.containsKey("key1")) {
            //todo...
    } }

}

More explanation

  • \ in yaml it is Used to break string into multiline

  • \" is escape charater for "(quote) in yaml string

  • {key:value} JSON in yaml which will be converted to Map by @Value

  • #{ } it is SpEL expresion and can be used in @Value to convert json int Map or Array / list Reference

Tested in a spring boot project

Holbein answered 1/5, 2020 at 0:20 Comment(0)
G
0

Use the same variable name as the Yaml name

Eg:

private final HashMap<String, String> expiration 

instead of

private final HashMap<String, String> expirations 
Greenes answered 31/7, 2020 at 23:42 Comment(0)
E
0

Or something similar to this in properties file

org.code=0009,0008,0010
org.code.0009.channel=30,40
org.code.0008.channel=30,40
org.code.0010.channel=30,40

in Java, read org.code and then loop thru each org.code and build org.code..channel and put it into a map....

Endoscope answered 24/3, 2022 at 17:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.