Injecting List with Spring from yaml

In my recent project, I need to fill a list from configuration file. With my old habits I just tried to use @Value annotation and my code just broke. Here is what I’ve tried first:

application.yaml:

segment:
  list:
    - SEG1
    - SEG2

AppRunner.java:

@Component
public class AppRunner implements ApplicationRunner {

    @Value("${segment.list}")
    private List<String> segmentList;

    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        segmentList.forEach(segment -> System.out.println(segment));
    }

}

However, it just exploded like below:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'segment.list' in string value "${segment.list}"
	at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:174) ~[spring-core-4.2.4.RELEASE.jar:4.2.4.RELEASE]
	at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126) ~[spring-core-4.2.4.RELEASE.jar:4.2.4.RELEASE]
	at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:204) ~[spring-core-4.2.4.RELEASE.jar:4.2.4.RELEASE]
	at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:178) ~[spring-core-4.2.4.RELEASE.jar:4.2.4.RELEASE]
	at org.springframework.context.support.PropertySourcesPlaceholderConfigurer$2.resolveStringValue(PropertySourcesPlaceholderConfigurer.java:172) ~[spring-context-4.2.4.RELEASE.jar:4.2.4.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:808) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1027) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1014) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:545) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
	... 23 common frames omitted

After that, I used a workaround for this problem and solve the issue with below annotation style on segmentList and application.yaml:

    @Value("#{'${segment.list}'.split(',')}")
    private List<String> segmentList;

application.yaml:

segment:
  list: SEG1,SEG2

I was basically, splitting a String with comma. It seems fine when I run the application. However, when I run my unit tests I get below error:

Unit test:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = App.class)
public class AppRunnerTest {

    @Autowired
    private AppRunner appRunner;

    @Test
    public void testRun() throws Exception {
        appRunner.run(null);
    }

}

Exception:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'segment.list' in string value "#{'${segment.list}'.split(',')}"
 at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:174)
 at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)
 at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:204)
 at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:178)
 at org.springframework.context.support.PropertySourcesPlaceholderConfigurer$2.resolveStringValue(PropertySourcesPlaceholderConfigurer.java:172)
 at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:808)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1027)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1014)
 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:545)
 ... 46 more

I’ve looked at a lot of annotation like @TestPropertySource and tried a lot of methods like injecting PropertyPlaceHolder to my test classes to run unit tests with this way but couldn’t manage it. The application context doesn’t aware of my properties because different application context is created when I run tests. At the end, I found this issue on Spring’s Jira system. That was the same issue I had and it guided me to @ConfigurationProperties annotation. And fortunately, I was using Spring Boot and already have this annotation in my path. With this guidance I add a new configuration class, changed application.yaml to list style again and get my list from configurations with the help of @EnableConfigurationProperties annotation at injection:

Config class:

@Configuration
@ConfigurationProperties(prefix = "segment")
public class SegmentListConfig {

    private List<String> list;

    SegmentListConfig() {
        this.list = new ArrayList<>();
    }

    public List<String> getList() {
        return this.list;
    }

}

Runner class:

@Component
@EnableConfigurationProperties
public class AppRunner implements ApplicationRunner {

    @Autowired
    private SegmentListConfig segmentListConfig;

    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        segmentListConfig.getList().forEach(segment -> System.out.println(segment));
    }

}

application.yaml:

segment:
  list:
    - SEG1
    - SEG2

When I dig into a little bit, I found that @ConfigurationProperties can do more. Here is a nice little blog post about how to use it.

I simulated my steps in different commits at this repository. You can check whole working code and not working codes from there.

Developers Rock!!!

This entry was posted in Java and tagged , , , , , , , , , , , , . Bookmark the permalink.

1 Response to Injecting List with Spring from yaml

  1. wayne libiski says:

    I came here with the same issue…thank you for this post

Leave a comment