From 12f228b5e4c6e6deef1c27f55ddd396c8ce3f61c Mon Sep 17 00:00:00 2001 From: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com> Date: Sun, 5 Jul 2026 15:05:29 +0900 Subject: [PATCH] Convert elements of collection- and array-valued query method parameters. Query method parameters exposed through Spring Data REST were passed to the backing repository without element conversion when the raw value already was an instance of the declared target type (e.g. a `List`). As a result, a repeated request parameter such as `?colors=RED&colors=GREEN` reached an `in`-style query as a `List` instead of a `List`, causing the persistence provider to fail. `ReflectionRepositoryInvoker` now also invokes conversion for array- and `Iterable`-typed parameters even when the value already matches the target type, so the raw elements get converted to the declared element type. --- .../support/ReflectionRepositoryInvoker.java | 9 +++++- .../ReflectionRepositoryInvokerUnitTests.java | 29 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java b/src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java index 142231bf39..5154d16090 100644 --- a/src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java +++ b/src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java @@ -46,6 +46,7 @@ * @author Oliver Gierke * @author Alessandro Nistico * @author Johannes Englmeier + * @author Seonwoo Jung * @since 1.10 */ class ReflectionRepositoryInvoker implements RepositoryInvoker { @@ -188,7 +189,13 @@ private Object[] prepareParameters(Method method, MultiValueMap rawPa Object value = unwrapSingleElement(rawParameters.get(parameterName)); - result[i] = targetType.isInstance(value) ? value : convert(value, param); + // Collection- and array-valued parameters must be converted even if the value already is an instance of + // the target type (e.g. a List), as the raw elements (typically String) still need to be converted to the + // declared element type (see GH-3502). + boolean elementConversionRequired = value != null + && (targetType.isArray() || Iterable.class.isAssignableFrom(targetType)); + + result[i] = (targetType.isInstance(value) && !elementConversionRequired) ? value : convert(value, param); } } diff --git a/src/test/java/org/springframework/data/repository/support/ReflectionRepositoryInvokerUnitTests.java b/src/test/java/org/springframework/data/repository/support/ReflectionRepositoryInvokerUnitTests.java index 5cdf97f32f..e0b04363f1 100755 --- a/src/test/java/org/springframework/data/repository/support/ReflectionRepositoryInvokerUnitTests.java +++ b/src/test/java/org/springframework/data/repository/support/ReflectionRepositoryInvokerUnitTests.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.AdditionalAnswers; +import org.mockito.ArgumentCaptor; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionService; @@ -52,6 +53,7 @@ * Integration tests for {@link ReflectionRepositoryInvoker}. * * @author Oliver Gierke + * @author Seonwoo Jung */ @ExtendWith(MockitoExtension.class) class ReflectionRepositoryInvokerUnitTests { @@ -221,6 +223,24 @@ void translatesCollectionRequestParametersCorrectly() throws Exception { } } + @Test // GH-3502 + void convertsElementsOfCollectionValuedQueryParameter() throws Exception { + + MultiValueMap parameters = new LinkedMultiValueMap<>(); + parameters.put("colors", Arrays.asList("RED", "GREEN")); + + var method = ColorRepository.class.getMethod("findByColorIn", Collection.class); + var repository = mock(ColorRepository.class); + + getInvokerFor(repository, expectInvocationOf(method)).invokeQueryMethod(method, parameters, Pageable.unpaged(), + Sort.unsorted()); + + var captor = ArgumentCaptor.forClass(Collection.class); + verify(repository).findByColorIn(captor.capture()); + + assertThat(captor.getValue()).containsExactly(Color.RED, Color.GREEN); + } + @Test // DATACMNS-700 void failedParameterConversionCapturesContext() throws Exception { @@ -371,4 +391,13 @@ interface DeleteByIdOverrideRepository extends Repository { } interface DeleteByIdOverrideSubRepository extends DeleteByIdOverrideRepository {} + + // GH-3502 + enum Color { + RED, GREEN; + } + + interface ColorRepository extends Repository { + List findByColorIn(@Param("colors") Collection colors); + } }