75

In my Spring Boot app I have application.yaml configuration file with following content. I want to have it injected as a Configuration object with list of channel configurations:

available-payment-channels-list:
  xyz: "123"
  channelConfigurations:
    -
      name: "Company X"
      companyBankAccount: "1000200030004000"
    -
      name: "Company Y"
      companyBankAccount: "1000200030004000"

And @Configuration object I want to be populated with list of PaymentConfiguration objects:

    @ConfigurationProperties(prefix = "available-payment-channels-list")
    @Configuration
    @RefreshScope
    public class AvailableChannelsConfiguration {

        private String xyz;

        private List<ChannelConfiguration> channelConfigurations;

        public AvailableChannelsConfiguration(String xyz, List<ChannelConfiguration> channelConfigurations) {
            this.xyz = xyz;
            this.channelConfigurations = channelConfigurations;
        }

        public AvailableChannelsConfiguration() {

        }

        // getters, setters


        @ConfigurationProperties(prefix = "available-payment-channels-list.channelConfigurations")
        @Configuration
        public static class ChannelConfiguration {
            private String name;
            private String companyBankAccount;

            public ChannelConfiguration(String name, String companyBankAccount) {
                this.name = name;
                this.companyBankAccount = companyBankAccount;
            }

            public ChannelConfiguration() {
            }

            // getters, setters
        }

    }

I am injecting this as a normal bean with @Autowired constructor. Value of xyz is populated correctly, but when Spring tries to parse yaml into list of objects I am getting

   nested exception is java.lang.IllegalStateException: 
    Cannot convert value of type [java.lang.String] to required type    
    [io.example.AvailableChannelsConfiguration$ChannelConfiguration] 
    for property 'channelConfigurations[0]': no matching editors or 
    conversion strategy found]

Any clues what is wrong here?

Tomasz Dziurko
  • 1,668
  • 2
  • 17
  • 21
  • I have the exact same problem : I tried a lot of things but I got an empty object, or either the same error `Cannot convert value of type ... String .... to MyObject ... no matching editors or conversion strategy found`. Weird thing is I didn't changed anything in Spring Boot that could affect SnakeYAML parameters or behaviour ... Actually, what the heck ? – Alex Jan 19 '16 at 09:56
  • I almost forgot, I'm using Spring Boot 1.2.6 – Alex Jan 19 '16 at 10:30
  • my guess would be the newline after the '-' characters in the yaml – davidfrancis Feb 04 '20 at 10:13

6 Answers6

22

The reason must be somewhere else. Using only Spring Boot 1.2.2 out of the box with no configuration, it Just Works. Have a look at this repo - can you get it to break?

https://github.com/konrad-garus/so-yaml

Are you sure the YAML file looks exactly the way you pasted? No extra whitespace, characters, special characters, mis-indentation or something of that sort? Is it possible you have another file elsewhere in the search path that is used instead of the one you're expecting?

Konrad Garus
  • 53,145
  • 43
  • 157
  • 230
  • I did the same and for clean SpringBoot project it works. It turned out that our customisation of property loading for different environments wasn't behaving properly. – Tomasz Dziurko Sep 16 '15 at 11:12
  • Hi @Konrad Garus, Please look into https://stackoverflow.com/questions/57409781/spring-boot-yaml-propertysource-configurationproperties-list-in-proper It's a similar issue. – Suresh M Sidy Aug 08 '19 at 11:23
20
  • You don't need constructors
  • You don't need to annotate inner classes
  • RefreshScope have some problems when using with @Configuration. Please see this github issue

Change your class like this:

@ConfigurationProperties(prefix = "available-payment-channels-list")
@Configuration
public class AvailableChannelsConfiguration {

    private String xyz;
    private List<ChannelConfiguration> channelConfigurations;

    // getters, setters

    public static class ChannelConfiguration {
        private String name;
        private String companyBankAccount;

        // getters, setters
    }

}
Conrad
  • 91
  • 2
  • 8
Gokhan Oner
  • 3,237
  • 19
  • 25
  • Same stuff, generally your code is only a simplification of mine nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [io.example.available_channels.AvailableChannelsConfiguration$ChannelConfiguration] for property 'channelConfigurations[0]': no matching editors or conversion strategy found] – Tomasz Dziurko Sep 15 '15 at 19:47
  • I ran the code & see all values assigned. Did u remove the RefreshScope annotation – Gokhan Oner Sep 15 '15 at 19:53
  • Re Gokhan: - Spring Boot ver. 1.2.2 - sprinc-core ver. 4.1.6 , all other spring libs are 4.1.5 or 4.1.6 (all taken from Spring Boot) – Tomasz Dziurko Sep 16 '15 at 05:31
  • Try to export ChannelConfiguration to its own class. Mark it with Component annotation. It also needs setters an getters for all fields. – hi_my_name_is Sep 16 '15 at 06:00
  • I checked it with that version – Gokhan Oner Sep 16 '15 at 06:00
  • Its working with 1.2.2. Also. Can u create an empty project and just put that to confirm its not related something else.?? – Gokhan Oner Sep 16 '15 at 06:01
  • This is what I was looking I am using Map instead of the list but during the test case is null. Can you please help me in this regard? – Manoj Sharma Mar 12 '21 at 10:38
14

I had referenced this article and many others and did not find a clear cut concise response to help. I am offering my discovery, arrived at with some references from this thread, in the following:

Spring-Boot version: 1.3.5.RELEASE

Spring-Core version: 4.2.6.RELEASE

Dependency Management: Brixton.SR1

The following is the pertinent yaml excerpt:

tools:
  toolList:
    - 
      name: jira
      matchUrl: http://someJiraUrl
    - 
      name: bamboo
      matchUrl: http://someBambooUrl

I created a Tools.class:

@Component
@ConfigurationProperties(prefix = "tools")
public class Tools{
    private List<Tool> toolList = new ArrayList<>();
    public Tools(){
      //empty ctor
    }

    public List<Tool> getToolList(){
        return toolList;
    }

    public void setToolList(List<Tool> tools){
       this.toolList = tools;
    }
}

I created a Tool.class:

@Component
public class Tool{
    private String name;
    private String matchUrl;

    public Tool(){
      //empty ctor
    }

    public String getName(){
        return name;
    }

    public void setName(String name){
       this.name= name;
    }
    public String getMatchUrl(){
        return matchUrl;
    }

    public void setMatchUrl(String matchUrl){
       this.matchUrl= matchUrl;
    }

    @Override
    public String toString(){
        StringBuffer sb = new StringBuffer();
        String ls = System.lineSeparator();
        sb.append(ls);
        sb.append("name:  " + name);
        sb.append(ls);
        sb.append("matchUrl:  " + matchUrl);
        sb.append(ls);
    }
}

I used this combination in another class through @Autowired

@Component
public class SomeOtherClass{

   private Logger logger = LoggerFactory.getLogger(SomeOtherClass.class);

   @Autowired
   private Tools tools;

   /* excluded non-related code */

   @PostConstruct
   private void init(){
       List<Tool>  toolList = tools.getToolList();
       if(toolList.size() > 0){
           for(Tool t: toolList){
               logger.info(t.toString());
           }
       }else{
           logger.info("*****-----     tool size is zero     -----*****");
       }  
   }

   /* excluded non-related code */

}

And in my logs the name and matching url's were logged. This was developed on another machine and thus I had to retype all of the above so please forgive me in advance if I inadvertently mistyped.

I hope this consolidation comment is helpful to many and I thank the previous contributors to this thread!

JavaJd
  • 445
  • 1
  • 7
  • 17
  • you don't need ``@Component`` annotation for Tool class – heroin Jan 02 '18 at 15:09
  • 2
    Do you really need a Tools class that is just a wrapper for a list? – Jeff Jun 27 '18 at 19:20
  • This solved it for me, especially the hint with the empty constructor for the tool class ... – SpaceMonkey Jan 15 '19 at 15:45
  • I had a "@AllArgsConstructor" on both the classes and that's what was causing the problem. Removed it and left the "@NorArgsConstructor" there, and it worked like a charm. FYI, I also have the getters and setter with "@Data" on both classes. – Piyush Nov 14 '19 at 14:59
9

I had much issues with this one too. I finally found out what's the final deal.

Referring to @Gokhan Oner answer, once you've got your Service class and the POJO representing your object, your YAML config file nice and lean, if you use the annotation @ConfigurationProperties, you have to explicitly get the object for being able to use it. Like :

@ConfigurationProperties(prefix = "available-payment-channels-list")
//@Configuration  <-  you don't specificly need this, instead you're doing something else
public class AvailableChannelsConfiguration {

    private String xyz;
    //initialize arraylist
    private List<ChannelConfiguration> channelConfigurations = new ArrayList<>();

    public AvailableChannelsConfiguration() {
        for(ChannelConfiguration current : this.getChannelConfigurations()) {
            System.out.println(current.getName()); //TADAAA
        }
    }

    public List<ChannelConfiguration> getChannelConfigurations() {
        return this.channelConfigurations;
    }

    public static class ChannelConfiguration {
        private String name;
        private String companyBankAccount;
    }

}

And then here you go. It's simple as hell, but we have to know that we must call the object getter. I was waiting at initialization, wishing the object was being built with the value but no. Hope it helps :)

Alex
  • 4,599
  • 4
  • 22
  • 42
5

I tried 2 solutions, both work.

Solution_1

.yml

available-users-list:
  configurations:
    -
      username: eXvn817zDinHun2QLQ==
      password: IP2qP+BQfWKJMVeY7Q==
    -
      username: uwJlOl/jP6/fZLMm0w==
      password: IP2qP+BQKJLIMVeY7Q==

LoginInfos.java

@ConfigurationProperties(prefix = "available-users-list")
@Configuration
@Component
@Data
public class LoginInfos {
    private List<LoginInfo> configurations;

    @Data
    public static class LoginInfo {
        private String username;
        private String password;
    }

}
List<LoginInfos.LoginInfo> list = loginInfos.getConfigurations();

Solution_2

.yml

available-users-list: '[{"username":"eXvn817zHBVn2QLQ==","password":"IfWKJLIMVeY7Q=="}, {"username":"uwJlOl/g9jP6/0w==","password":"IP2qWKJLIMVeY7Q=="}]'

Java

@Value("${available-users-listt}")
String testList;

ObjectMapper mapper = new ObjectMapper();
LoginInfos.LoginInfo[] array = mapper.readValue(testList, LoginInfos.LoginInfo[].class);

Vikki
  • 1,897
  • 1
  • 17
  • 24
  • `@ConfigurationProperties` creates a bean that you can autowire. you don't need to use `@Component` or `@Configuration` if you have `@ConfigurationProperties` already – rslvn Dec 20 '22 at 15:06
0

for me the fix was to add the injected class as inner class in the one annotated with @ConfigurationProperites, because I think you need @Component to inject properties.

Bashar Ali Labadi
  • 1,014
  • 1
  • 11
  • 19