(First, sorry to anyone who tried to read this before I reformatted the code blocks with triple backticks instead of the single backticks provided by the button in the editor widget.)
I discovered this problem while moving some Spring XML configs over to Spring Boot yaml format.
Tested with spring-boot-starter-parent 2.1.6.RELEASE and 2.2.4.RELEASE. Reproducible in both.
Attempting to parse a map of the form:
someMap:
"*": "value"
Results in ConverterNotFoundException.
However other examples of maps that parse fine are the following: - Add another entry; problematic single entry unchanged
someMap:
"a": "b"
"*": "value"
- Single entry, key is not "*":
someMap:
"a": "value"
The target class for the configs is of the form:
public class SomeProperties
{
// various other members, booleans, Strings, etc.
private Map<String, String> someMap = newHashMap();
...
public void setSomeMap( Map<String, String> someMap )
{
this.someMap = someMap;
}
...
And various other complex configs mapping to this class work just fine. Only the one with a single map entry with a key value of "*" fails.
I cannot change the key value, because application semantics of existing code depend on that, but I can work around the problem in this case by adding a junk map entry so it has two entries and thus parses successfully, even though I only need the one entry. However other people might encounter this bug in cases where that work around is not viable for them.
The lowest level exception signature in the stack trace is the following:
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [java.util.Map<java.lang.String, java.lang.String>]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:321)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:194)
at org.springframework.boot.context.properties.bind.BindConverter$CompositeConversionService.convert(BindConverter.java:170)
at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:96)
at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:88)
at org.springframework.boot.context.properties.bind.MapBinder.bindAggregate(MapBinder.java:64)
at org.springframework.boot.context.properties.bind.AggregateBinder.bind(AggregateBinder.java:56)
at org.springframework.boot.context.properties.bind.Binder.lambda$bindAggregate$2(Binder.java:293)
at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:429)
at org.springframework.boot.context.properties.bind.Binder$Context.access$100(Binder.java:372)
at org.springframework.boot.context.properties.bind.Binder.bindAggregate(Binder.java:293)
at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:254)
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:214)
... 118 more
See attachment for fuller stack trace. spring-boot-bug.txt
Comment From: snicoll
Thanks for the report. I've pushed a sample based on your description in https://github.com/snicoll-scratches/gh-20246
Comment From: chzhyu
test:
map:
"[*]": "value"
You may use [] to indicate an index of map.
Comment From: plusgrade-jn
Thanks for the suggestion. That does work.
And please see attached screen shot (only one entry in the map, no entry with key "*"
) which should help indicate the nature of the partner problem(s) and hopefully provide insight to fix. The parser apparently silently discards map entries where key is simply "*"
without brackets, hence the error absent an additional entry. However I would assert that it is incorrect or at least dangerous behavior for the parser to silently throw out the "*" keyed entry.
Comment From: plusgrade-jn
test: map: "[*]": "value"
You may use [] to indicate an index of map.
@chzhyu Can you provide a link to documentation that describes this convention?
I've looked at a number of resources describing map key syntax, and I don't see this convention discussed in any of them.
Comment From: mbhave
@plusgrade-jn This is covered in the documentation in the Relaxed Binding section
When binding to Map properties, if the key contains anything other than lowercase alpha-numeric characters or -, you need to use the bracket notation so that the original value is preserved. If the key is not surrounded by [], any characters that are not alpha-numeric or - are removed.
@snicoll I'm not sure if I follow what the bug is in this case. Wouldn't the key required surrounding it with [ ]
for it to work?
Comment From: snicoll
Yes, I wish the exception was a bit more clear though. Sorry about that!
Comment From: plusgrade-jn
@mbhave Thank you for the link to the relevant documentation section.
@snicoll I think it is premature to close this bug, though perhaps the title / content should be adjusted (or a new bug created). Part of the problem I faced in this case is that this happened:
any characters that are not alpha-numeric or - are removed
producing an empty key, however in cases where there was a second entry in the map, the parser did not emit any error or warning related to the fact that it completely disposed of one of the map entries due to the key being empty / invalid. Minimally there should have been a loud warning emitted. Ideally I think an exception should have been thrown with a clear message concerning the problematic key construction.
Comment From: mbhave
@plusgrade-jn I think we can tackle that as part of #13404. This issue is pretty much a duplicate of that and the reason why it was closed.