diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java index 3912156671b..46b3ed27148 100644 --- a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java @@ -649,6 +649,11 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA else if (value instanceof List list) { int index = Integer.parseInt(key); growCollectionIfNecessary(list, index, indexedPropertyName.toString(), ph, i + 1); + if (index < 0 || index >= list.size()) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Cannot get element with index " + index + " from List of size " + + list.size() + ", accessed using property path '" + propertyName + "'"); + } value = list.get(index); } else if (value instanceof Map map) { diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java index 2b3480404fa..33194ed8400 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java @@ -16,6 +16,7 @@ package org.springframework.beans; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -183,7 +184,34 @@ class BeanWrapperAutoGrowingTests { wrapper.setAutoGrowCollectionLimit(2); assertThatExceptionOfType(InvalidPropertyException.class) .isThrownBy(() -> wrapper.getPropertyValue("list[4]")) - .withRootCauseInstanceOf(IndexOutOfBoundsException.class); + .withMessageContainingAll( + "Invalid property 'list[4]'", + "Cannot get element with index 4 from List of size 0"); + } + + @Test + void getPropertyValueSelfPopulatingListWorksWithinLimit() { + bean.setList(new SelfPopulatingList()); + assertThat(wrapper.getPropertyValue("list[2]")).isInstanceOf(Bean.class); + assertThat(bean.getList()) + .hasSize(3) + .allSatisfy(entry -> assertThat(entry).isInstanceOf(Bean.class)); + } + + @Test + void getPropertyValueSelfPopulatingListFailsAgainstLimit() { + bean.setList(new SelfPopulatingList()); + wrapper.setAutoGrowCollectionLimit(2); + assertThatExceptionOfType(InvalidPropertyException.class) + .isThrownBy(() -> wrapper.getPropertyValue("list[4]")); + } + + @Test + void setPropertyValueSelfPopulatingListFailsAgainstLimitForNestedPath() { + bean.setList(new SelfPopulatingList()); + wrapper.setAutoGrowCollectionLimit(2); + assertThatExceptionOfType(InvalidPropertyException.class) + .isThrownBy(() -> wrapper.setPropertyValue("list[4].prop", "test")); } @Test @@ -382,4 +410,24 @@ class BeanWrapperAutoGrowingTests { } } + + /** + * A {@link List} implementation that creates elements on demand in {@link #get(int)} + * instead of throwing {@link IndexOutOfBoundsException} for out-of-range indexes. + * + *

Used to verify that {@link BeanWrapperImpl} does not delegate to + * {@link List#get(int)} for indexes beyond the configured auto-grow limit. + */ + @SuppressWarnings("serial") + private static class SelfPopulatingList extends ArrayList { + + @Override + public Bean get(int index) { + while (size() <= index) { + add(new Bean()); + } + return super.get(index); + } + } + }