Reflection on records
Tagir Valeev
amaembo at gmail.com
Tue Dec 2 09:48:12 UTC 2025
Ok, I've managed to implement a working Babylon-based solution. Here's the
library code:
package org.example;
import jdk.incubator.code.Quoted;
import jdk.incubator.code.Reflect;
import jdk.incubator.code.dialect.core.CoreOp;
import jdk.incubator.code.dialect.java.JavaOp;
import jdk.incubator.code.dialect.java.MethodRef;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.RecordComponent;
import java.util.Arrays;
public final class ReflectUtil {
@Reflect
@FunctionalInterface
public interface RecordAccessor<T extends Record> {
/**
* @param r record type
* @return result of the record component
* @param <R> an unused generic parameter to avoid accidental
use with lambda
* (lambdas cannot bind to generic SAM).
* Only method references are supported.
*/
<R> Object get(T r);
}
/**
* @param accessor a method reference bound to a record accessor
* @return record component that corresponds to a given accessor
* @param <T> record type
* @throws IllegalArgumentException if the supplied argument is
not a record accessor method reference
* @throws RuntimeException if the record accessor cannot be
resolved (e.g., due to insufficient access rights)
*/
public static <T extends Record> RecordComponent
component(RecordAccessor<T> accessor) {
Quoted quoted = CoreOp.QuotedOp.ofQuotable(accessor)
.orElseThrow(() -> new
IllegalArgumentException("Accessor must be quotable"));
if (!(quoted.op() instanceof JavaOp.LambdaOp lambda)) {
throw new IllegalArgumentException("Expected method reference");
}
JavaOp.InvokeOp invokeOp = lambda.methodReference()
.orElseThrow(() -> new
IllegalArgumentException("Expected method reference"));
if (invokeOp.invokeKind() != JavaOp.InvokeOp.InvokeKind.INSTANCE) {
throw new IllegalArgumentException("Method reference bound
to record accessor is expected");
}
MethodRef descriptor = invokeOp.invokeDescriptor();
Method method;
try {
method =
descriptor.resolveToMethod(MethodHandles.privateLookupIn(accessor.getClass(),
MethodHandles.lookup()));
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Cannot resolve descriptor
"+descriptor, e);
}
Class<?> recordClass = method.getDeclaringClass();
if (!recordClass.isRecord()) {
throw new IllegalArgumentException("Not a record accessor method");
}
return Arrays.stream(recordClass.getRecordComponents()).filter(rc
-> rc.getAccessor().equals(method))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Not a
record accessor: "+method));
}
}
It could be used like this:
package org.example;
public class Main {
private record MyRecord(int comp, double comp2) {}
static void main() {
IO.println(ReflectUtil.component(MyRecord::comp));
IO.println(ReflectUtil.component(MyRecord::comp2));
}
}
You have to open your module to code-reflection though:
module reflectRecords {
requires jdk.incubator.code;
opens org.example to jdk.incubator.code;
}
With best regards,
Tagir Valeev
On Mon, Dec 1, 2025 at 3:09 PM Tagir Valeev <amaembo at gmail.com> wrote:
> I don't think there are any published JEPs. Here's some information about
> the project:
> https://openjdk.org/projects/babylon/
>
> On Mon, Dec 1, 2025, 15:08 David Alayachew <davidalayachew at gmail.com>
> wrote:
>
>> Understood. Thanks for clarifying. For now, I'll hold off on that, as I
>> don't know how stable that is, and I don't really want to.build this new
>> library off of it. Maybe when the respective JEP goes into preview. Could
>> you link me to that JEP, or is there not one yet?
>>
>> On Mon, Dec 1, 2025, 9:01 AM Tagir Valeev <amaembo at gmail.com> wrote:
>>
>>> Yes, you have to build it from the Babylon repo.
>>>
>>> On Mon, Dec 1, 2025, 14:35 David Alayachew <davidalayachew at gmail.com>
>>> wrote:
>>>
>>>> Thanks @Tagir Valeev <amaembo at gmail.com>.
>>>>
>>>> I am still not great at the stuff Babylon is doing, so I can only
>>>> roughly follow along. Regardless, it sounds like this is depending on
>>>> Babylon features which have not yet landed in the mainline jdk, yes?
>>>>
>>>>
>>>> On Mon, Dec 1, 2025, 5:22 AM Tagir Valeev <amaembo at gmail.com> wrote:
>>>>
>>>>> I think this will surely be possible for RecordComponents with code
>>>>> reflection (project Babylon), using the syntax you propose.
>>>>>
>>>>> You can create a quotable function like this:
>>>>>
>>>>> import jdk.incubator.code.*;
>>>>>
>>>>> @Reflect
>>>>> @FunctionalInterface
>>>>> interface Accessor {
>>>>> Object get(Record r);
>>>>> }
>>>>>
>>>>> Then create something like (just a sketch, sorry, I have no time now
>>>>> to provide a full-fledged sample):
>>>>>
>>>>> static RecordComponent foo(Accessor acc) {
>>>>> Quoted q = Op.ofQuotable(acc).orElseThrow();
>>>>> // extract a method handle from q using Babylon API
>>>>> // unreflect it and find the corresponding RecordComponent
>>>>> }
>>>>>
>>>>> With best regards,
>>>>> Tagir Valeev
>>>>>
>>>>> On Sat, Nov 29, 2025 at 8:45 PM David Alayachew <
>>>>> davidalayachew at gmail.com> wrote:
>>>>>
>>>>>> Hello @amber-dev <amber-dev at openjdk.org>,
>>>>>>
>>>>>> I asked this on core-libs-dev already, but I figured I should ask
>>>>>> here too.
>>>>>>
>>>>>> Let's say I have some record User(String firstName, String lastName)
>>>>>> {}.
>>>>>>
>>>>>> Is there any possible way for me to do the following?
>>>>>>
>>>>>> java.lang.reflect.RecordComponent firstName = foo(User::firstName);
>>>>>>
>>>>>> I'll even accept this.
>>>>>>
>>>>>> java.lang.reflect.Method lastName = foo(User::lastName);
>>>>>>
>>>>>> Thank you for your time and consideration.
>>>>>> David Alayachew
>>>>>>
>>>>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20251202/445b2f3d/attachment-0001.htm>
More information about the amber-dev
mailing list