From ee49c4bbe7fc9a3c6a688282ba0a0bd80c5334da Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 2 Mar 2026 11:35:32 +0100 Subject: [PATCH 1/3] Prepare issue branch. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9104c8adb6..4fd6f486f7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-commons - 4.1.0-SNAPSHOT + 4.1.0-TPP-NATIVE-SNAPSHOT Spring Data Core Core Spring concepts underpinning every Spring Data module. From 86db7f97622f818b320601bf14723fdf38af1465 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 2 Mar 2026 11:51:13 +0100 Subject: [PATCH 2/3] Add `TypedPropertyPathFeature` to lambda reachability metadata. We now provide a feature that introspects TypedPropertyPath and PropertyReference lambdas and registers Graal metadata for lambdas and the referenced class. --- pom.xml | 16 +- .../data/core/TypedPropertyPathFeature.java | 147 ++++++++++++++++++ .../native-image.properties | 1 + 3 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/springframework/data/core/TypedPropertyPathFeature.java create mode 100644 src/main/resources/META-INF/native-image/org.springframework.data/spring-data-commons/native-image.properties diff --git a/pom.xml b/pom.xml index 4fd6f486f7..8c43c9cb94 100644 --- a/pom.xml +++ b/pom.xml @@ -116,7 +116,7 @@ true - + io.projectreactor @@ -130,7 +130,7 @@ test - + io.reactivex.rxjava3 @@ -235,6 +235,7 @@ + org.codehaus.groovy groovy-all @@ -243,6 +244,7 @@ + org.jetbrains.kotlin kotlin-stdlib @@ -288,6 +290,7 @@ + org.scala-lang scala-library @@ -295,6 +298,15 @@ true + + + + org.graalvm.sdk + graal-sdk + 25.0.2 + provided + + jakarta.transaction jakarta.transaction-api diff --git a/src/main/java/org/springframework/data/core/TypedPropertyPathFeature.java b/src/main/java/org/springframework/data/core/TypedPropertyPathFeature.java new file mode 100644 index 0000000000..fd3eb3b291 --- /dev/null +++ b/src/main/java/org/springframework/data/core/TypedPropertyPathFeature.java @@ -0,0 +1,147 @@ +/* + * Copyright 2026-present 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.core; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.function.BiConsumer; + +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeReflection; +import org.graalvm.nativeimage.hosted.RuntimeSerialization; + +import org.springframework.beans.BeanUtils; + +/** + * GraalVM {@link Feature} that registers serializable {@link TypedPropertyPath} and {@link PropertyReference} lambdas. + * This allows to use typed property paths and property references in native images without the need to pre-compute them + * at build time. + *

+ * This feature also registers Java Bean Properties (methods and fields) referenced by the property path or reference + * for reflection, so that they are available at runtime and therefore the underlying domain model does not require + * additional reachability configuration. + * + * @author Mark Paluch + * @since 4.1 + */ +class TypedPropertyPathFeature implements Feature { + + /** + * Token indicating a class is or is not a lambda. + */ + private static final String LAMBDA_CLASS_MARKER = "$$Lambda"; + + /** + * The offset from {@link #LAMBDA_CLASS_MARKER} where the end marker is found. + */ + private static final int LAMBDA_CLASS_END_MARKER = LAMBDA_CLASS_MARKER.length(); + + private final SerializableLambdaReader reader = new SerializableLambdaReader(); + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + + BiConsumer> serializableLambdaHandler = (ignore, cls) -> { + + if (isLambdaClass(cls)) { + + try { + registerLambdaSerialization(cls); + registerDomainModel(cls); + } catch (Exception e) { + e.printStackTrace(); + } + } + }; + + access.registerSubtypeReachabilityHandler(serializableLambdaHandler, TypedPropertyPath.class); + access.registerSubtypeReachabilityHandler(serializableLambdaHandler, PropertyReference.class); + } + + private void registerLambdaSerialization(Class lambdaClass) { + + RuntimeSerialization.register(lambdaClass); + RuntimeReflection.registerMethodLookup(lambdaClass, "writeReplace"); + } + + private void registerDomainModel(Class cls) throws ReflectiveOperationException { + Constructor declaredConstructor = cls.getDeclaredConstructor(); + declaredConstructor.setAccessible(true); + Object lambdaInstance = declaredConstructor.newInstance(); + + MemberDescriptor memberDescriptor = reader.read(lambdaInstance); + registerDomainModel(memberDescriptor); + } + + private static void registerDomainModel(MemberDescriptor descriptor) { + + PropertyDescriptor property = null; + + if (descriptor.getMember() instanceof Field f) { + RuntimeReflection.register(f); + property = BeanUtils.getPropertyDescriptor(descriptor.getOwner(), f.getName()); + } + + if (descriptor.getMember() instanceof Method m) { + RuntimeReflection.register(m); + property = BeanUtils.findPropertyForMethod(m); + } + + if (property != null) { + Method readMethod = property.getReadMethod(); + Method writeMethod = property.getWriteMethod(); + + if (readMethod != null) { + RuntimeReflection.register(readMethod); + } + if (writeMethod != null) { + RuntimeReflection.register(writeMethod); + } + + RuntimeReflection.registerFieldLookup(descriptor.getOwner(), property.getName()); + } + } + + /** + * Return true if the specified Class represents a raw lambda. + * + * @param cls class to inspect. + * @return true if the class represents a raw lambda. + */ + public static boolean isLambdaClass(Class cls) { + + String name = cls.getName(); + int marker = name.indexOf(LAMBDA_CLASS_MARKER); + if (marker == -1) { + return false; + } + + int noffset = marker + LAMBDA_CLASS_END_MARKER; + if (noffset > name.length()) { + return false; + } + + char c = name.charAt(noffset); + + // '$' character will be seen in releases between Java {8,20} + // '/' is used in Java 21 + // See bug 35177243 + return c == '$' || c == '/'; + } + +} diff --git a/src/main/resources/META-INF/native-image/org.springframework.data/spring-data-commons/native-image.properties b/src/main/resources/META-INF/native-image/org.springframework.data/spring-data-commons/native-image.properties new file mode 100644 index 0000000000..c35440e649 --- /dev/null +++ b/src/main/resources/META-INF/native-image/org.springframework.data/spring-data-commons/native-image.properties @@ -0,0 +1 @@ +Args=--features=org.springframework.data.core.TypedPropertyPathFeature From bc89d93e4ce3d90a6da933c81dc6f6f1ecfa4829 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 3 Mar 2026 14:05:35 +0100 Subject: [PATCH 3/3] Document TypedPropertyPathFeature. --- src/main/antora/modules/ROOT/pages/property-paths.adoc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/antora/modules/ROOT/pages/property-paths.adoc b/src/main/antora/modules/ROOT/pages/property-paths.adoc index ab1e046b3e..eff4b69ce9 100644 --- a/src/main/antora/modules/ROOT/pages/property-paths.adoc +++ b/src/main/antora/modules/ROOT/pages/property-paths.adoc @@ -201,5 +201,7 @@ Limiting property paths to a specific domain type that is used within the curren + Whenever accepting or providing multiple property paths, consider using `TypedPropertyPath` to allow for properties within the context of the owning type `T` to limit property paths to a common owning type. -NOTE: When using Graal Native Image compilation, you need to provide reachability metadata for serializable `TypedPropertyPath` lambdas. -When using lambda expressions instead of method references you will have to include the Java source code of the class containing the lambda expression in the native image configuration. +NOTE: Graal Native Image compilation requires reachability metadata for serializable `TypedPropertyPath` lambdas. +Spring Data ships a built-in https://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/hosted/Feature.html[feature] through `org.springframework.data.core.TypedPropertyPathFeature`. +The feature is auto-activated and registers required serialization- and reflection metadata for lambda parsing and referenced reflective members (fields and methods). +Note also, when using lambda expressions instead of method references you will have to include the Java source code of the class containing the lambda expression in the native image configuration yourself.