[java] Mapping list in Yaml to list of objects in Spring Boot

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?

This question is related to java spring spring-boot yaml

The answer is


  • 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
    }

}

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!


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);


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 :)


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.


Examples related to java

Under what circumstances can I call findViewById with an Options Menu / Action Bar item? How much should a function trust another function How to implement a simple scenario the OO way Two constructors How do I get some variable from another class in Java? this in equals method How to split a string in two and store it in a field How to do perspective fixing? String index out of range: 4 My eclipse won't open, i download the bundle pack it keeps saying error log

Examples related to spring

Are all Spring Framework Java Configuration injection examples buggy? Two Page Login with Spring Security 3.2.x Access blocked by CORS policy: Response to preflight request doesn't pass access control check Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean Failed to auto-configure a DataSource: 'spring.datasource.url' is not specified Spring Data JPA findOne() change to Optional how to use this? After Spring Boot 2.0 migration: jdbcUrl is required with driverClassName The type WebMvcConfigurerAdapter is deprecated No converter found capable of converting from type to type

Examples related to spring-boot

Access blocked by CORS policy: Response to preflight request doesn't pass access control check Why am I getting Unknown error in line 1 of pom.xml? Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured How to resolve Unable to load authentication plugin 'caching_sha2_password' issue ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean Failed to auto-configure a DataSource: 'spring.datasource.url' is not specified After Spring Boot 2.0 migration: jdbcUrl is required with driverClassName ERROR Source option 1.5 is no longer supported. Use 1.6 or later How to start up spring-boot application via command line? JSON parse error: Can not construct instance of java.time.LocalDate: no String-argument constructor/factory method to deserialize from String value

Examples related to yaml

Use placeholders in yaml How to test that a registered variable is not empty? YAML equivalent of array of objects in JSON How to set multiple commands in one yaml file with Kubernetes? Mapping list in Yaml to list of objects in Spring Boot YAML mapping values are not allowed in this context Setting active profile and config location from command line in spring boot Using Docker-Compose, how to execute multiple commands Check if a list contains an item in Ansible Converting Swagger specification JSON to HTML documentation