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