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