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!!!
I came here with the same issue…thank you for this post