diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandler.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandler.java index 4f1ab5e123b..6f175a84658 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandler.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandler.java @@ -106,7 +106,23 @@ public class NoUnboundElementsBindHandler extends AbstractBindHandler { private boolean isUnbound(ConfigurationPropertyName name, ConfigurationPropertyName candidate) { - return name.isAncestorOf(candidate) && !this.boundNames.contains(candidate); + if (name.isAncestorOf(candidate)) { + if (!this.boundNames.contains(candidate) + && !isOverriddenCollectionElement(candidate)) { + return true; + } + } + return false; + } + + private boolean isOverriddenCollectionElement(ConfigurationPropertyName candidate) { + int length = candidate.getNumberOfElements(); + if (candidate.isNumericIndex(length - 1)) { + ConfigurationPropertyName propertyName = candidate + .chop(candidate.getNumberOfElements() - 1); + return this.boundNames.contains(propertyName); + } + return false; } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandlerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandlerTests.java index 93bf00bf06f..2d71ff56215 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandlerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandlerTests.java @@ -108,6 +108,35 @@ public class NoUnboundElementsBindHandlerTests { assertThat(bound.getFoo()).isEqualTo("bar"); } + @Test + public void bindWhenUsingNoUnboundElementsHandlerShouldBindIfUnboundCollectionProperties() { + MockConfigurationPropertySource source1 = new MockConfigurationPropertySource(); + source1.put("example.foo[0]", "bar"); + MockConfigurationPropertySource source2 = new MockConfigurationPropertySource(); + source2.put("example.foo[0]", "bar"); + source2.put("example.foo[1]", "baz"); + this.sources.add(source1); + this.sources.add(source2); + this.binder = new Binder(this.sources); + NoUnboundElementsBindHandler handler = new NoUnboundElementsBindHandler(); + ExampleWithList bound = this.binder + .bind("example", Bindable.of(ExampleWithList.class), handler).get(); + assertThat(bound.getFoo()).containsExactly("bar"); + } + + @Test + public void bindWhenUsingNoUnboundElementsHandlerAndUnboundListElementsShouldThrowException() { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("example.foo[0]", "bar"); + this.sources.add(source); + this.binder = new Binder(this.sources); + assertThatExceptionOfType(BindException.class) + .isThrownBy(() -> this.binder.bind("example", Bindable.of(Example.class), + new NoUnboundElementsBindHandler())) + .satisfies((ex) -> assertThat(ex.getCause().getMessage()) + .contains("The elements [example.foo[0]] were left unbound")); + } + public static class Example { private String foo; @@ -122,4 +151,18 @@ public class NoUnboundElementsBindHandlerTests { } + public static class ExampleWithList { + + private List foo; + + public List getFoo() { + return this.foo; + } + + public void setFoo(List foo) { + this.foo = foo; + } + + } + }