From 481a5743b35fe6eab474e9d32860522dd802554e Mon Sep 17 00:00:00 2001 From: Matthias Kurz Date: Tue, 2 Jun 2026 12:25:19 +0200 Subject: [PATCH] Apply auto-grow limit to direct field binding DataBinder applies its auto-grow collection limit to bean property access, but direct field access left DirectFieldAccessor at its default limit. Pass DataBinder's configured limit into DirectFieldBindingResult and apply it to the DirectFieldAccessor. Closes gh-36861 Signed-off-by: Matthias Kurz --- .../validation/DataBinder.java | 2 +- .../validation/DirectFieldBindingResult.java | 19 +++++++++++ .../DataBinderFieldAccessTests.java | 33 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/spring-context/src/main/java/org/springframework/validation/DataBinder.java b/spring-context/src/main/java/org/springframework/validation/DataBinder.java index bd804bfe647..37f93d18224 100644 --- a/spring-context/src/main/java/org/springframework/validation/DataBinder.java +++ b/spring-context/src/main/java/org/springframework/validation/DataBinder.java @@ -341,7 +341,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { */ protected AbstractPropertyBindingResult createDirectFieldBindingResult() { DirectFieldBindingResult result = new DirectFieldBindingResult(getTarget(), - getObjectName(), isAutoGrowNestedPaths()); + getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit()); if (this.conversionService != null) { result.initConversion(this.conversionService); diff --git a/spring-context/src/main/java/org/springframework/validation/DirectFieldBindingResult.java b/spring-context/src/main/java/org/springframework/validation/DirectFieldBindingResult.java index 9e57053ac29..68f7537354c 100644 --- a/spring-context/src/main/java/org/springframework/validation/DirectFieldBindingResult.java +++ b/spring-context/src/main/java/org/springframework/validation/DirectFieldBindingResult.java @@ -19,6 +19,7 @@ package org.springframework.validation; import org.jspecify.annotations.Nullable; import org.springframework.beans.ConfigurablePropertyAccessor; +import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.PropertyAccessorFactory; /** @@ -41,6 +42,8 @@ public class DirectFieldBindingResult extends AbstractPropertyBindingResult { private final boolean autoGrowNestedPaths; + private final int autoGrowCollectionLimit; + private transient @Nullable ConfigurablePropertyAccessor directFieldAccessor; @@ -60,9 +63,24 @@ public class DirectFieldBindingResult extends AbstractPropertyBindingResult { * @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value */ public DirectFieldBindingResult(@Nullable Object target, String objectName, boolean autoGrowNestedPaths) { + this(target, objectName, autoGrowNestedPaths, Integer.MAX_VALUE); + } + + /** + * Create a new {@code DirectFieldBindingResult} for the given target. + * @param target the target object to bind onto + * @param objectName the name of the target object + * @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value + * @param autoGrowCollectionLimit the limit for array and collection auto-growing + * @since 7.1 + */ + public DirectFieldBindingResult(@Nullable Object target, String objectName, + boolean autoGrowNestedPaths, int autoGrowCollectionLimit) { + super(objectName); this.target = target; this.autoGrowNestedPaths = autoGrowNestedPaths; + this.autoGrowCollectionLimit = autoGrowCollectionLimit; } @@ -82,6 +100,7 @@ public class DirectFieldBindingResult extends AbstractPropertyBindingResult { this.directFieldAccessor = createDirectFieldAccessor(); this.directFieldAccessor.setExtractOldValueForEditor(true); this.directFieldAccessor.setAutoGrowNestedPaths(this.autoGrowNestedPaths); + ((DirectFieldAccessor) this.directFieldAccessor).setAutoGrowCollectionLimit(this.autoGrowCollectionLimit); } return this.directFieldAccessor; } diff --git a/spring-context/src/test/java/org/springframework/validation/DataBinderFieldAccessTests.java b/spring-context/src/test/java/org/springframework/validation/DataBinderFieldAccessTests.java index cc1e22ad51c..c2c925b02e3 100644 --- a/spring-context/src/test/java/org/springframework/validation/DataBinderFieldAccessTests.java +++ b/spring-context/src/test/java/org/springframework/validation/DataBinderFieldAccessTests.java @@ -17,10 +17,12 @@ package org.springframework.validation; import java.beans.PropertyEditorSupport; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; +import org.springframework.beans.InvalidPropertyException; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.NotWritablePropertyException; import org.springframework.beans.NullValueInNestedPathException; @@ -134,6 +136,25 @@ class DataBinderFieldAccessTests { binder.bind(pvs)); } + @Test + void directFieldAccessHonorsDefaultAutoGrowCollectionLimit() { + FieldAccessForm target = new FieldAccessForm(); + DataBinder binder = new DataBinder(target); + binder.initDirectFieldAccess(); + + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("items[255].name", "value"); + binder.bind(pvs); + + assertThat(target.items).hasSize(256); + assertThat(target.items.get(255).name).isEqualTo("value"); + + MutablePropertyValues outOfBounds = new MutablePropertyValues(); + outOfBounds.add("items[256].name", "too-far"); + assertThatExceptionOfType(InvalidPropertyException.class).isThrownBy(() -> + binder.bind(outOfBounds)); + } + @Test void bindingWithErrorsAndCustomEditors() { FieldAccessBean rod = new FieldAccessBean(); @@ -176,4 +197,16 @@ class DataBinderFieldAccessTests { assertThat(tb.getSpouse()).isNotNull(); }); } + + + static class FieldAccessForm { + + public List items; + } + + + static class FieldAccessItem { + + public String name; + } }