Skip to content

Commit 98829cc

Browse files
quaffmp911de
authored andcommitted
Throw exception if Kotlin projection requires non-null value but null result present
Fix GH-3242 Signed-off-by: Yanming Zhou <[email protected]>
1 parent ff0fcdb commit 98829cc

File tree

3 files changed

+66
-1
lines changed

3 files changed

+66
-1
lines changed

src/main/java/org/springframework/data/projection/ProjectingMethodInterceptor.java

+16-1
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,21 @@
1616
package org.springframework.data.projection;
1717

1818
import java.lang.reflect.Array;
19+
import java.lang.reflect.Method;
1920
import java.util.Arrays;
2021
import java.util.Collection;
2122
import java.util.Collections;
2223
import java.util.List;
2324
import java.util.Map;
2425
import java.util.Map.Entry;
2526

27+
import kotlin.reflect.KFunction;
2628
import org.aopalliance.intercept.MethodInterceptor;
2729
import org.aopalliance.intercept.MethodInvocation;
2830
import org.springframework.core.CollectionFactory;
31+
import org.springframework.core.KotlinDetector;
2932
import org.springframework.core.convert.ConversionService;
33+
import org.springframework.data.util.KotlinReflectionUtils;
3034
import org.springframework.data.util.NullableWrapper;
3135
import org.springframework.data.util.NullableWrapperConverters;
3236
import org.springframework.data.util.TypeInformation;
@@ -44,6 +48,7 @@
4448
* @author Mark Paluch
4549
* @author Christoph Strobl
4650
* @author Johannes Englmeier
51+
* @author Yanming Zhou
4752
* @since 1.10
4853
*/
4954
class ProjectingMethodInterceptor implements MethodInterceptor {
@@ -64,11 +69,13 @@ class ProjectingMethodInterceptor implements MethodInterceptor {
6469
@Override
6570
public Object invoke(@SuppressWarnings("null") @NonNull MethodInvocation invocation) throws Throwable {
6671

67-
TypeInformation<?> type = TypeInformation.fromReturnTypeOf(invocation.getMethod());
72+
Method method = invocation.getMethod();
73+
TypeInformation<?> type = TypeInformation.fromReturnTypeOf(method);
6874
TypeInformation<?> resultType = type;
6975
TypeInformation<?> typeToReturn = type;
7076

7177
Object result = delegate.invoke(invocation);
78+
7279
boolean applyWrapper = false;
7380

7481
if (NullableWrapperConverters.supports(type.getType())
@@ -83,6 +90,14 @@ public Object invoke(@SuppressWarnings("null") @NonNull MethodInvocation invocat
8390
return conversionService.convert(new NullableWrapper(result), typeToReturn.getType());
8491
}
8592

93+
if (result == null) {
94+
KFunction<?> function = KotlinDetector.isKotlinType(method.getDeclaringClass()) ?
95+
KotlinReflectionUtils.findKotlinFunction(method) : null;
96+
if (function != null && !function.getReturnType().isMarkedNullable()) {
97+
throw new IllegalArgumentException("Kotlin function '%s' requires non-null return value".formatted(method.toString()));
98+
}
99+
}
100+
86101
return result;
87102
}
88103

src/test/java/org/springframework/data/projection/ProjectingMethodInterceptorUnitTests.java

+25
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
* @author Saulo Medeiros de Araujo
4444
* @author Mark Paluch
4545
* @author Christoph Strobl
46+
* @author Yanming Zhou
4647
*/
4748
@ExtendWith(MockitoExtension.class)
4849
class ProjectingMethodInterceptorUnitTests {
@@ -204,6 +205,30 @@ void returnsEnumSet() throws Throwable {
204205
assertThat(collection).containsOnly(HelperEnum.Helpful);
205206
}
206207

208+
@Test
209+
void throwExceptionIfKotlinProjectionRequiresNonNullWithNullResult() throws Throwable {
210+
211+
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor,
212+
conversionService);
213+
214+
when(invocation.getMethod()).thenReturn(Person.class.getMethod("getName"));
215+
when(interceptor.invoke(invocation)).thenReturn(null);
216+
217+
assertThatIllegalArgumentException().isThrownBy(() -> methodInterceptor.invoke(invocation));
218+
}
219+
220+
@Test
221+
void returnsNullIfKotlinProjectionDoesNotRequiresNonNullWithNullResult() throws Throwable {
222+
223+
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor,
224+
conversionService);
225+
226+
when(invocation.getMethod()).thenReturn(Person.class.getMethod("getAge"));
227+
when(interceptor.invoke(invocation)).thenReturn(null);
228+
229+
assertThat(methodInterceptor.invoke(invocation)).isNull();
230+
}
231+
207232
/**
208233
* Mocks the {@link Helper} method of the given name to return the given value.
209234
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2024-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.projection
18+
19+
/**
20+
* @author Yanming Zhou
21+
*/
22+
interface Person {
23+
val name: String
24+
val age: Int?
25+
}

0 commit comments

Comments
 (0)