I have a declaration in a POJO that looks something like this:
@Value("${prefix.someList}")
private List<String> list;
I have a line in a properties file that looks like this:
prefix.list = some,stuff,with,comma,separators
I also added a @PostConstruct
method to the POJO, only so I could add a line I could set a breakpoint on.
When I hit the breakpoint, I look at the "list" property, and it has a SINGLE entry with the value "some,stuff,with,comma,separators
".
I also added the following declaration:
@Value("${prefix.someList}")
private String[] array;
At the breakpoint, the array had 5 entries, with the expected values.
If I convert the first expression to
"#{'${prefix.list}'.split(',')}"
Then it does produce a list with the expected entries, but I'd prefer not to do that. It's a little more complicated, and doesn't defend against empty list properties.
I then did some googling for this problem. I saw suggestions that I should add a "ConversionService
" bean, instantiated with "new DefaultConversionService()
". I did this. I also discovered the references in there for "StringToCollectionConverter
" and "StringToArrayConverter
". I set breakpoints in expected places in these classes.
I found the DefaultConversionService
being created, along with the two converter classes. However, the "convert" methods in those classes was never called, even the "array" one, even though something was doing the conversion.
I then even commented out my Bean that creates the DefaultConversionService
, and I found the result was identical (I noticed the constructor was being hit twice).
So, in short, conversion of string to list is not happening, even though string to array is, but I have no idea what mechanism is supposed to be doing this, because neither of the expected converters seem to even be used, even though the array conversion is being done somewhere.
I'm using Spring 5.0.9 with Java 8.
To be clear, the other post that is allegedly a duplicate of this does not help. First, the "answer" is simply a suboptimal workaround that really shouldn't be necessary. The other suggestion in that post didn't make any difference.
Update:
It was suggested that I should construct a minimal reproducible example. Fortunately, start.spring.io makes it simple to produce a Spring boot skeleton (I didn't mention before that this is a Spring Boot app).
I took the sample maven and java8 project. Note that I selected version 2.1.0, but my own project is using 2.0.5.
I added a dummy class, added a @Component annotation, added a list and array property, a @PostConstruct method that prints the list and length of the list, and defined a csv list property in application.properties.
I ran it. It did what you said it would. It worked fine, producing a list with as many elements as there were in the csv list. It was curious, however, that it still doesn't appear to be calling the converters in the conversion service. None of the breakpoints in the converter classes were hit.
Then, I changed the spring boot version in the pom from 2.1.0 to 2.0.5 and reran my test. Boom. I got exactly the behavior I've been seeing. it produced a list with one element, where the string was the entire csv list.
So, it appears that something was changed or fixed between 2.0.5 and 2.1.0 that made this work properly.
I can't upgrade right now, so I can't use this solution. I would search through the spring-boot change log, but as I never saw it hit the expected converter classes, I wonder what is actually doing this.
Update:
If it matters, here's my simple pojo that I added to the springboot skeleton:
package com.example.demo;
import java.util.List;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Stuff {
@Value("${stuffList}")
private List<String> stuffList;
@Value("${stuffList}")
private String[] stuffArray;
public final List<String> getStuffList() { return stuffList; }
public final void setStuffList(List<String> stuffList) { this.stuffList = stuffList; }
public final String[] getStuffArray() { return stuffArray; }
public final void setStuffArray(String[] stuffArray) { this.stuffArray = stuffArray; }
@PostConstruct
public void stuff() {
System.out.println("stuffList[" + stuffList + "] len[" + stuffList.size() + "]");
}
}
With Springboot 2.1.0, the len is 3. With 2.0.5, the len is 1. Neither of them call "`org.springframework.core.convert.support.StringToCollectionConverter.convert(Object, TypeDescriptor, TypeDescriptor)'"
And this is the main class, unmodified from the skeleton.
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Update:
Ok, the solution is described at Reading a List from properties file and load with spring annotation @Value , but it's not the accepted answer. The correct answer is the one that describes defining the bean of type ConversionService with an object of type DefaultConversionService.
What can be confusing is that if you set some breakpoints in DefaultConversionService before adding that bean, you will hit them. However, it appears that Spring uses that class by default for other things besides property value resolution. Once I added the bean (and I did it in the correct class), that got the StringToCollection converter to work properly.