From 450a8b607003ef12dc6aa18cebde0a8f5c3b1c70 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 bd804bfe6479..37f93d182249 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 void initDirectFieldAccess() { */ 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 9e57053ac293..68f7537354c3 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 @@ 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 DirectFieldBindingResult(@Nullable Object target, String objectName) { * @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 final ConfigurablePropertyAccessor getPropertyAccessor() { 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 cc1e22ad51c9..c2c925b02e3a 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 @@ void nestedBindingWithDisabledAutoGrow() { 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 @@ public String getAsText() { assertThat(tb.getSpouse()).isNotNull(); }); } + + + static class FieldAccessForm { + + public List items; + } + + + static class FieldAccessItem { + + public String name; + } }