When the value of the map is of type java.time.Duration(According to the source code, it seems that this problem exists for non Java.lang class types), it will not be mapped correctly if "[]" is not used on the key which has the dot in the yaml file. Just like this:
leo:
yyy:
expiry: #Map<String, Duration>
"[http.server.requests]": 5ms # it's fine
http.server.request: 5ms # don't work
integer-test:
"[http.server.requests]": 3 # it's fine
http.server.request: 4 # it's fine
If the value type in map is Integer or Boolean or other types of java.lang, their keys can be mapped correctly without brackets. If it is confirmed that it is a bug and can make relevant suggestions, I will be very happy to fix it. Here is the test case: Case
Comment From: snicoll
Please review the documentation before reporting an issue, there is an explicit section about it.
Comment From: wilkinsona
This doesn't feel quite right to me. I would argue that Duration is a scalar data type, so, as currently written, the documentation implies to me that there should be no need for bracket notation. I think it'd be nice if the runtime's behaviour matched the documentation as there doesn't appear to be any ambiguity that could result in a nested map being created.
Here's a complete example:
package com.example.demo;
import java.time.Duration;
import java.util.Map;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties(ExampleProperties.class)
public class Gh27581Application {
public Gh27581Application(ExampleProperties properties) {
System.out.println(properties.getAlpha());
System.out.println(properties.getBravo());
}
public static void main(String[] args) {
new SpringApplicationBuilder(Gh27581Application.class)
.properties(
"example.alpha.one:1s",
"example.alpha.two.three:23s",
"example.alpha.[four.five]:45s",
"example.bravo.one:1",
"example.bravo.two.three:23",
"example.bravo.[four.five]:45")
.run(args);
}
}
@ConfigurationProperties("example")
class ExampleProperties {
private Map<String, Duration> alpha;
private Map<String, Integer> bravo;
public Map<String, Duration> getAlpha() {
return alpha;
}
public void setAlpha(Map<String, Duration> alpha) {
this.alpha = alpha;
}
public Map<String, Integer> getBravo() {
return bravo;
}
public void setBravo(Map<String, Integer> bravo) {
this.bravo = bravo;
}
}
example.alpha.two.three:23s is silently ignored:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.3)
2021-08-09 13:50:47.629 INFO 14581 --- [ main] com.example.demo.Gh27581Application : Starting Gh27581Application using Java 11.0.10 on wilkinsona-a01.vmware.com with PID 14581 (/Users/awilkinson/dev/workspaces/spring-projects/spring-boot/main/gh-27581/target/classes started by awilkinson in /Users/awilkinson/dev/workspaces/spring-projects/spring-boot/main/gh-27581)
2021-08-09 13:50:47.630 INFO 14581 --- [ main] com.example.demo.Gh27581Application : No active profile set, falling back to default profiles: default
{four.five=PT45S, one=PT1S}
{one=1, four.five=45, two.three=23}
2021-08-09 13:50:47.984 INFO 14581 --- [ main] com.example.demo.Gh27581Application : Started Gh27581Application in 0.632 seconds (JVM running for 0.903)
Comment From: polarbear567
@wilkinsona means exactly what I want to say.
Comment From: wilkinsona
example.alpha.two.three:23s is dropped because Duration doesn't have a default constructor. This means that JavaBeanBinder cannot create a Bean for it so bind returns null:
https://github.com/spring-projects/spring-boot/blob/9822cad51053c5886c27ac025c42f1a3580b470f/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java#L50-L61
Things then unwind gracefully from here.
When handling the null bind result, no attempt is made to create a Duration as create is false when binding an aggregate:
https://github.com/spring-projects/spring-boot/blob/9822cad51053c5886c27ac025c42f1a3580b470f/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java#L428-L436
Comment From: wilkinsona
With ignoreUnknownFields set to false example.alpha.two.three=23s causes a failure:
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target [Bindable@74518890 type = com.example.demo.ExampleProperties, value = 'provided', annotations = array<Annotation>[@org.springframework.boot.context.properties.ConfigurationProperties(ignoreInvalidFields=false, ignoreUnknownFields=false, prefix=example, value=example)]] failed:
Property: example.alpha.two.three
Value: 23s
Origin: "example.alpha.two.three" from property source "defaultProperties"
Reason: The elements [example.alpha.two.three] were left unbound.
Action:
Update your application's configuration
This doesn't feel quite right to me as the field isn't really unknown. It feels like it should always fail.
Comment From: philwebb
I've updated this one to a documentation issue. We'll improve this section to list the scalar types we support.
I've also created and linked a few related issues (#14796, #27695 and #14796).