From fb240829b3ce4a893f77612b6557eaf014ea389a Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 21 Aug 2026 14:08:24 +0200 Subject: [PATCH] Align SpEL's default max auto-grow size with Spring data binding Prior to this commit, the SpelParserConfiguration constructors that omit an explicit maximumAutoGrowSize left collection auto-growing effectively unbounded, defaulting to Integer.MAX_VALUE. That default was inconsistent with the auto-grow limit applied elsewhere in the framework for data binding (see DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT). To address that, this commit introduces a new SpelParserConfiguration.DEFAULT_MAX_AUTO_GROW_SIZE constant (set to 256 to match DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT) and switches the constructors that previously hard-coded Integer.MAX_VALUE to use this new default instead. Constructors that accept an explicit maximumAutoGrowSize are unaffected. In addition, SpelParserConfiguration now enforces that a user-supplied maximumAutoGrowSize is not a negative value, consistent with the preconditions already enforced for maximumExpressionLength, maximumOperations, maximumBigPowerBits, and maximumNestingDepth. A value of 0 remains supported (effectively disabling collection auto-growing) and is now documented as such in the Javadoc. The Spring Framework reference documentation has also been updated to describe the new default, and tests have been added to IndexingTests to verify the default, the ability to override it, and the new precondition. Closes gh-36995 --- .../pages/core/expressions/evaluation.adoc | 8 +++ .../spel/SpelParserConfiguration.java | 44 +++++++++++--- .../expression/spel/IndexingTests.java | 59 +++++++++++++++++++ 3 files changed, 102 insertions(+), 9 deletions(-) diff --git a/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc b/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc index ca5d2b830d0..e1e161bc574 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc @@ -554,6 +554,14 @@ Kotlin:: ---- ====== +When collection auto-growing is enabled, a collection cannot automatically grow beyond +256 elements by default; however, the `maximumAutoGrowSize` value is configurable. This +default is aligned with `DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT`, for consistency +with the auto-grow limit used for data binding in Spring MVC and Spring WebFlux. If you +create a `SpelExpressionParser` programmatically, you can specify a custom +`maximumAutoGrowSize` when creating the `SpelParserConfiguration` that you provide to the +`SpelExpressionParser`. + By default, a SpEL expression cannot contain more than 10,000 characters; however, the `maxExpressionLength` is configurable. If you create a `SpelExpressionParser` programmatically, you can specify a custom `maxExpressionLength` when creating the diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java index c62119f8fe1..175132a2b02 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java @@ -36,6 +36,15 @@ import org.springframework.util.StringUtils; */ public class SpelParserConfiguration { + /** + * Default maximum size to which a collection or array can automatically grow: {@value}. + *

Aligned with the default auto-grow limit used for Spring's data binding + * support (see {@code DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT}), for + * consistency between SpEL and data binding. + * @since 7.1 + */ + public static final int DEFAULT_MAX_AUTO_GROW_SIZE = 256; + /** * Default maximum length permitted for a SpEL expression: {@value}. * @since 5.2.24 @@ -162,9 +171,10 @@ public class SpelParserConfiguration { * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME + * @see #DEFAULT_MAX_AUTO_GROW_SIZE */ public SpelParserConfiguration() { - this(null, null, false, false, Integer.MAX_VALUE); + this(null, null); } /** @@ -180,9 +190,10 @@ public class SpelParserConfiguration { * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME + * @see #DEFAULT_MAX_AUTO_GROW_SIZE */ public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader) { - this(compilerMode, compilerClassLoader, false, false, Integer.MAX_VALUE); + this(compilerMode, compilerClassLoader, false, false, DEFAULT_MAX_AUTO_GROW_SIZE); } /** @@ -196,9 +207,10 @@ public class SpelParserConfiguration { * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME + * @see #DEFAULT_MAX_AUTO_GROW_SIZE */ public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowCollections) { - this(null, null, autoGrowNullReferences, autoGrowCollections, Integer.MAX_VALUE); + this(autoGrowNullReferences, autoGrowCollections, DEFAULT_MAX_AUTO_GROW_SIZE); } /** @@ -209,7 +221,9 @@ public class SpelParserConfiguration { * global defaults per use case. * @param autoGrowNullReferences if null references should automatically grow * @param autoGrowCollections if collections should automatically grow - * @param maximumAutoGrowSize the maximum size to which a collection can auto grow + * @param maximumAutoGrowSize the maximum size to which a collection can auto grow; + * must not be negative, and a value of {@code 0} effectively disables growing + * a collection beyond its current size * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME @@ -230,7 +244,9 @@ public class SpelParserConfiguration { * expression compilation; or {@code null} to use the default {@code ClassLoader} * @param autoGrowNullReferences if null references should automatically grow * @param autoGrowCollections if collections should automatically grow - * @param maximumAutoGrowSize the maximum size to which a collection can auto grow + * @param maximumAutoGrowSize the maximum size to which a collection can auto grow; + * must not be negative, and a value of {@code 0} effectively disables growing + * a collection beyond its current size * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME @@ -254,7 +270,9 @@ public class SpelParserConfiguration { * expression compilation; or {@code null} to use the default {@code ClassLoader} * @param autoGrowNullReferences if null references should automatically grow * @param autoGrowCollections if collections should automatically grow - * @param maximumAutoGrowSize the maximum size to which a collection can auto grow + * @param maximumAutoGrowSize the maximum size to which a collection can auto grow; + * must not be negative, and a value of {@code 0} effectively disables growing + * a collection beyond its current size * @param maximumExpressionLength the maximum length of a SpEL expression; * must be a positive number * @since 5.2.25 @@ -282,7 +300,9 @@ public class SpelParserConfiguration { * expression compilation; or {@code null} to use the default {@code ClassLoader} * @param autoGrowNullReferences if null references should automatically grow * @param autoGrowCollections if collections should automatically grow - * @param maximumAutoGrowSize the maximum size to which a collection can auto grow + * @param maximumAutoGrowSize the maximum size to which a collection can auto grow; + * must not be negative, and a value of {@code 0} effectively disables growing + * a collection beyond its current size * @param maximumExpressionLength the maximum length of a SpEL expression; * must be a positive number * @param maximumOperations the maximum number of operations permitted during @@ -311,7 +331,9 @@ public class SpelParserConfiguration { * expression compilation; or {@code null} to use the default {@code ClassLoader} * @param autoGrowNullReferences if null references should automatically grow * @param autoGrowCollections if collections should automatically grow - * @param maximumAutoGrowSize the maximum size to which a collection can auto grow + * @param maximumAutoGrowSize the maximum size to which a collection can auto grow; + * must not be negative, and a value of {@code 0} effectively disables growing + * a collection beyond its current size * @param maximumExpressionLength the maximum length of a SpEL expression; * must be a positive number * @param maximumOperations the maximum number of operations permitted during @@ -338,7 +360,9 @@ public class SpelParserConfiguration { * expression compilation; or {@code null} to use the default {@code ClassLoader} * @param autoGrowNullReferences if null references should automatically grow * @param autoGrowCollections if collections should automatically grow - * @param maximumAutoGrowSize the maximum size to which a collection can auto grow + * @param maximumAutoGrowSize the maximum size to which a collection can auto grow; + * must not be negative, and a value of {@code 0} effectively disables growing + * a collection beyond its current size * @param maximumExpressionLength the maximum length of a SpEL expression; * must be a positive number * @param maximumOperations the maximum number of operations permitted during @@ -355,6 +379,7 @@ public class SpelParserConfiguration { int maximumOperations, int maximumBigPowerBits, int maximumNestingDepth) { Assert.notNull(compilerMode, "'compilerMode' must not be null"); + Assert.isTrue(maximumAutoGrowSize >= 0, "'maximumAutoGrowSize' must not be negative"); Assert.isTrue(maximumExpressionLength > 0, "'maximumExpressionLength' must be a positive number"); Assert.isTrue(maximumOperations > 0, "'maximumOperations' must be a positive number"); Assert.isTrue(maximumBigPowerBits > 0, "'maximumBigPowerBits' must be a positive number"); @@ -402,6 +427,7 @@ public class SpelParserConfiguration { /** * Return the maximum size to which a collection can auto grow. + * @see #DEFAULT_MAX_AUTO_GROW_SIZE */ public int getMaximumAutoGrowSize() { return this.maximumAutoGrowSize; diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java index 618e3450506..b44e24c923e 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java @@ -55,6 +55,7 @@ import org.springframework.expression.spel.testresources.Person; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; @@ -66,6 +67,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.springframework.expression.spel.SpelMessage.EXCEPTION_DURING_INDEX_READ; import static org.springframework.expression.spel.SpelMessage.EXCEPTION_DURING_INDEX_WRITE; import static org.springframework.expression.spel.SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE; +import static org.springframework.expression.spel.SpelMessage.UNABLE_TO_GROW_COLLECTION; import static org.springframework.expression.spel.SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE; @SuppressWarnings("rawtypes") @@ -326,6 +328,63 @@ class IndexingTests { .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE)); } + @Nested // gh-36995 + class MaxAutoGrowSizeTests { + + @Test + void defaultMaxAutoGrowSizeMatchesDataBinderDefault() { + SpelParserConfiguration configuration = new SpelParserConfiguration(true, true); + assertThat(configuration.getMaximumAutoGrowSize()) + .isEqualTo(SpelParserConfiguration.DEFAULT_MAX_AUTO_GROW_SIZE) + .isEqualTo(256); + } + + @Test + void zeroMaximumAutoGrowSizeIsAllowedAndDisablesGrowth() { + decimals = new ArrayList<>(); + SpelParserConfiguration configuration = new SpelParserConfiguration(true, true, 0); + SpelExpressionParser parser = new SpelExpressionParser(configuration); + + Expression indexExpression = parser.parseExpression("decimals[0]"); + assertThatExceptionOfType(SpelEvaluationException.class) + .isThrownBy(() -> indexExpression.getValue(IndexingTests.this)) + .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(UNABLE_TO_GROW_COLLECTION)); + } + + @Test + void negativeMaximumAutoGrowSizeIsRejected() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new SpelParserConfiguration(true, true, -1)) + .withMessage("'maximumAutoGrowSize' must not be negative"); + } + + @Test + void collectionGrowsUpToDefaultMaxAutoGrowSizeButNotBeyond() { + decimals = new ArrayList<>(); + SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + + // Growing up to the default maximum auto-grow size should succeed. + parser.parseExpression("decimals[255]").getValue(IndexingTests.this); + assertThat(decimals).hasSize(256); + + // Growing beyond the default maximum auto-grow size should fail. + Expression indexExpression = parser.parseExpression("decimals[256]"); + assertThatExceptionOfType(SpelEvaluationException.class) + .isThrownBy(() -> indexExpression.getValue(IndexingTests.this)) + .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(UNABLE_TO_GROW_COLLECTION)); + } + + @Test + void collectionCanGrowBeyondDefaultMaxAutoGrowSizeWhenConfiguredExplicitly() { + decimals = new ArrayList<>(); + SpelParserConfiguration configuration = new SpelParserConfiguration(true, true, 1000); + SpelExpressionParser parser = new SpelExpressionParser(configuration); + + parser.parseExpression("decimals[500]").getValue(IndexingTests.this); + assertThat(decimals).hasSize(501); + } + } + @Test void indexIntoGenericPropertyContainingArray() { String[] property = { "bar" };