Adam Berlin opened SPR-11759 and commented

Yaml file —


foobar:  
  ignoredUserIds:
    - 57016311
    - 22588218

Class —



public class Foobar {
    @Value("${foobar.ignoredUserIds}")
    List<String> ignoredUserIds;
}

Error —


Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: java.util.List foobar.Foobar.ignoredUserIds; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'foobar.ignoredUserIds' in string value "${foobar.ignoredUserIds}"


Reference URL: https://github.com/spring-projects/spring-boot/issues/501

21 votes, 21 watchers

Comment From: spring-projects-issues

Adam Berlin commented

Any thoughts?

Ping.

Comment From: spring-projects-issues

Adam Berlin commented

This feature would be nice for one of our current projects. We have a little hack to get around this limitation, but we'd love to get rid of it.

Comment From: spring-projects-issues

Adil Fulara commented

@Adam Berlin

Would you mind sharing the "hack" please ?

Adil

Comment From: spring-projects-issues

Adam Berlin commented

@Adil, I no longer have access to our client's codebase, so I cannot provide the workaround.

As I remember, we configured the list with a comma-delimited string and tokenized the string into a list. We'd definitely prefer that the standard YAML syntax for creating a list would translate into a List\ when using @Value.


Adam

Comment From: spring-projects-issues

Bob Tiernay commented

This would certainly be a welcomed addition to @Value semantics. Please see this thread for further context:

https://github.com/spring-projects/spring-boot/issues/501

Comment From: spring-projects-issues

Tongliang Liu commented

I think at least "one-level" array should be supported.

By "one-level", I mean each item in the array is of a simple type (integer, boolean, or string) and all items are of the same type in the array, for example:

oneLevelIntegerArray:
  - 1
  - 2
  - 3
oneLevelStringArray:
  - foo
  - bar

Therefore, in the code, one can use the following code to bind the above YAML arrays to a List:

    @Value("${oneLevelIntegerArray}")
    List<Integer> intArray;

    @Value("${oneLevelStringArray}")
    List<String> strArray;

But in order to bind more complex array structures, i.e. items in the array are of compound types (arrays, maps, etc..), one should use @ConfigurationProperties.

If this is acceptable, I'll create a pull request to make this happen. It is fairly easy to make this happen.

Comment From: spring-projects-issues

Diego Magalhaes commented

Hey guys,

Just encountered  that and got help in the gitter spring-boot channel that led me here.

Sample code:

@Data
@RefreshScope
@ConfigurationProperties(prefix = "api")
@Configuration
public class APIProperties {
    private Config config;

    @Data
    public static class Config {
        private Blocked blocked;

        @Data
        public static class Blocked {
            List<String> blockedExtensions = new ArrayList<>();
        }
    }
}

Looking at :8080/env shows that ConfigurationProperties worked as expected:

applicationConfig: [classpath:/application.yml]: {
    api.config.blocked.extensions[0]: ".exe",
    api.config.blocked.extensions[1]: ".dll",
    api.config.blocked.extensions[2]: ".bin",
    api.config.blocked.extensions[3]: ".dat",
    api.config.blocked.extensions[4]: ".osx"
}

now the controler:

@RestController
public class SampleServices{

    private APIProperties props;

    @Autowired
    public ShorteningService(@Qualifier("APIProperties") APIProperties apiProperties) {
        this.props = apiProperties;
    }

    @RequestMapping("test")
    Callable<String> test() {
        return () -> props.getConfig().getBlocked().getBlockedExtensions().toString();
    }
}

Calling /test returns "[ ]", an empty list.

So to verify that @Value won't work as well, let's change it for:

@RestController
public class ShorteningService {
    @Value("${api.config.blocked.extensions}")
    List<String> blockedExtensions;

    @RequestMapping("test")
    Callable<String> test() {
        return () -> blockedExtensions.toString();
    }
}

Error:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'api.config.blocked.extensions' in string value "${api.config.blocked.extensions}"
    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:175)
    at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:807)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:980)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:967)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:543)

Changing the controller code to:

@RestController
public class ShorteningService {
    @Value("${api.config.blocked.extensions[0]}")
    String blockedExtensions;

    @RequestMapping("test")
    Callable<String> test() {
        return () -> blockedExtensions;
    }
}

Works like charm, for the .yaml bellow, the result is ".exe"

api:
  config:
    blocked:
      extensions:
      - .exe
      - .dll
      - .bin
      - .dat
      - .osx

Comment From: spring-projects-issues

Stéphane Nicoll commented

Yeah well, your code there reproduces the limitation described in this issue. And this issue is not fixed so what you're experiencing is pretty much the current state yeah.

You are using Spring Boot so please do you a favour and stop using @Value. @ConfigurationProperties does a lot more and does support that use case.

Comment From: spring-projects-issues

Wenjie Zhang commented

I encountered this problem when I was using spring framework to load yaml configuration:

@Bean
public static PropertySourcesPlaceholderConfigurer properties() {
    final PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
    final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
    yaml.setResources(new ClassPathResource("application.yml"));
    propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
    return propertySourcesPlaceholderConfigurer;
}

//The property is 
@Value("${platforms}")
private List<String> platforms;

I can see the properties get loaded into the PropertySources object like platforms[0], platforms[1].

However, I still get following exception:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'platforms' in string value "${platforms}"
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:174)
    at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)

Comment From: spring-projects-issues

Wenjie Zhang commented

Do you guys think making the @Value to support regular expression is a good idea to resolve this bug?

Comment From: spring-projects-issues

Romero Ricardo commented

Yeah, 2017 and I'm still facing that same issue, the bug is not fixed. Do you guys think you can assign this issue to me so I get it sorted?

Comment From: spring-projects-issues

Stéphane Nicoll commented

Feel free to give that a try Romero, no need to be assigned for that.

Comment From: spring-projects-issues

Bulk closing outdated, unresolved issues. Please, reopen if still relevant.

Comment From: abccbaandy

2019 and I need this feature. @Value >>>>> @ConfigurationProperties when there is only one field : private List<String> someList;

Why I need a Class when I only have one field?

Comment From: komidawi

2021 and it's still needed 😉

Comment From: mimfgg

how hard can it be ... a bit later in 2021 and still stumbling on this ...

Comment From: aorizzuto

Replace this:

foobar:  
  ignoredUserIds:
    - 57016311
    - 22588218

with this:

foobar:  
  ignoredUserIds: 57016311 , 22588218

You need to separate the items with a comma. After that, you can do this:

public class Foobar {
    @Value("${foobar.ignoredUserIds}")
    List<String> ignoredUserIds;
}

Comment From: biuwsi

Damn, spend 2 hours to find out, that it's not I am dumb ass, it's just a "feature"!

Comment From: devdynam0507

2022 it's still needed 😀

Comment From: alexeytokar

2023 ;)

Comment From: xzxiaoshan

2023.11.2 Needed, looking forward to early support

Comment From: eritpchy

2024😩

Comment From: chillb0nes

+1 for this

Comment From: marcelstoer

This just got me... The current behavior is inconsistent and totally unexpected. Further up it says

Please, reopen if still relevant.

but none of us affected (no even the OP) can reopen it because it was created by bot.