diff --git a/src/main/java/org/springframework/data/repository/query/ReturnedType.java b/src/main/java/org/springframework/data/repository/query/ReturnedType.java index eb44d85a32..840eff42ba 100644 --- a/src/main/java/org/springframework/data/repository/query/ReturnedType.java +++ b/src/main/java/org/springframework/data/repository/query/ReturnedType.java @@ -24,6 +24,9 @@ import java.util.Map; import java.util.Set; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.model.PreferredConstructorDiscoverer; @@ -47,6 +50,8 @@ */ public abstract class ReturnedType { + private static final Log logger = LogFactory.getLog(ReturnedType.class); + private static final Map cache = new ConcurrentReferenceHashMap<>(32); private final Class domainType; @@ -294,10 +299,21 @@ private List detectConstructorParameterNames(Class type) { return Collections.emptyList(); } - List properties = new ArrayList<>(constructor.getConstructor().getParameterCount()); + int parameterCount = constructor.getConstructor().getParameterCount(); + List properties = new ArrayList<>(parameterCount); for (Parameter parameter : constructor.getParameters()) { - properties.add(parameter.getName()); + if (parameter.getName() != null) { + properties.add(parameter.getName()); + } + } + + if (properties.isEmpty() && parameterCount > 0) { + if (logger.isWarnEnabled()) { + logger.warn(("No constructor parameter names discovered. " + + "Compile the affected code with '-parameters' instead or avoid its introspection: %s") + .formatted(type.getName())); + } } return Collections.unmodifiableList(properties); diff --git a/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java b/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java index 4dfe80d71f..818cf09a11 100755 --- a/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java +++ b/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java @@ -167,6 +167,25 @@ void cachesInstancesBySourceTypes() { assertThat(left).isSameAs(right); } + @Test // GH-3225 + void detectsKotlinInputProperties() { + + var factory = new SpelAwareProxyProjectionFactory(); + + var returnedType = ReturnedType.of(SomeDataClass.class, Sample.class, factory); + + assertThat(returnedType.getInputProperties()).containsExactly("firstname", "lastname"); + } + + @Test // GH-3225 + void detectsKotlinValueClassInputProperties() { + + var factory = new SpelAwareProxyProjectionFactory(); + + var returnedType = ReturnedType.of(SomeDataClassWithValues.class, Sample.class, factory); + assertThat(returnedType.getInputProperties()).containsExactly("email", "firstname", "lastname"); + } + private static ReturnedType getReturnedType(String methodName, Class... parameters) throws Exception { return getQueryMethod(methodName, parameters).getResultProcessor().getReturnedType(); } diff --git a/src/test/kotlin/org/springframework/data/repository/query/SomeDataClass.kt b/src/test/kotlin/org/springframework/data/repository/query/SomeDataClass.kt new file mode 100644 index 0000000000..c4675e8ca0 --- /dev/null +++ b/src/test/kotlin/org/springframework/data/repository/query/SomeDataClass.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.repository.query + +/** + * @author Mark Paluch + */ +data class SomeDataClass(val firstname: String, val lastname: String = "Doe") + +@JvmInline +value class Email(val value: String) + +data class SomeDataClassWithValues( + val email: Email, + val firstname: String, + val lastname: String = "Doe" +)