diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java index 555efafc20b..61c68169646 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java @@ -551,7 +551,7 @@ public class Binder { this.configurationProperty = configurationProperty; } - private void clearConfigurationProperty() { + void clearConfigurationProperty() { this.configurationProperty = null; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ValueObjectBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ValueObjectBinder.java index 8b3277f0d9a..833afdfa1f8 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ValueObjectBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ValueObjectBinder.java @@ -61,6 +61,7 @@ class ValueObjectBinder implements DataObjectBinder { arg = (arg != null) ? arg : parameter.getDefaultValue(context.getConverter()); args.add(arg); } + context.clearConfigurationProperty(); return bound ? valueObject.instantiate(args) : null; } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java index 48aa5dfabe7..2eb46837a0b 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import com.atomikos.util.Assert; import org.junit.jupiter.api.Test; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; @@ -29,6 +30,7 @@ import org.springframework.boot.context.properties.source.MockConfigurationPrope import org.springframework.format.annotation.DateTimeFormat; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link ValueObjectBinder}. @@ -220,6 +222,20 @@ class ValueObjectBinderTests { assertThat(bean.getDate().toString()).isEqualTo("2019-05-10"); } + @Test + void bindWhenAllPropertiesBoundShouldClearConfigurationProperty() { // gh-18704 + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("foo.bar", "hello"); + this.sources.add(source); + Bindable target = Bindable.of(ValidatingConstructorBean.class); + assertThatExceptionOfType(BindException.class).isThrownBy(() -> this.binder.bind("foo", target)) + .satisfies(this::noConfigurationProperty); + } + + private void noConfigurationProperty(BindException ex) { + assertThat(ex.getProperty()).isNull(); + } + static class ExampleValueBean { private final int intValue; @@ -413,4 +429,26 @@ class ValueObjectBinderTests { } + static class ValidatingConstructorBean { + + private final String foo; + + private final String bar; + + ValidatingConstructorBean(String foo, String bar) { + Assert.notNull("Foo must not be null", foo); + this.foo = foo; + this.bar = bar; + } + + String getFoo() { + return this.foo; + } + + String getBar() { + return this.bar; + } + + } + }