Reflection on records

Stephen Colebourne scolebourne at joda.org
Mon Dec 1 02:30:23 UTC 2025


You'll need to write the annotation processor yourself I'm afraid, this is
a design outline.
Stephen

On Sun, 30 Nov 2025, 21:19 David Alayachew, <davidalayachew at gmail.com>
wrote:

> Thanks Stephen.
>
> Hmmmmm, so this is definitely the best solution I have seen thus far. The
> only real downside is that the static final field has a different name than
> the actual record component. But I can probably fix that by making it an
> inner class or something, as opposed to sharing the same namespace as the
> record itself.
>
> Can you link me to the annotation code?
>
> On Sun, Nov 30, 2025 at 4:05 PM Stephen Colebourne <scolebourne at joda.org>
> wrote:
>
>> There is a way to do this, but it isn't pretty. Use annotation processing.
>>
>> @GeneratedRecordComponentInterface
>> record User(String firstName, String lastName, ComplexObject
>> complexColumn) implements UserColumns {}
>>
>> // annotation processor generates:
>> public interface UserColumns {
>>   public static final RecordComponentWrapper<String>
>> FIRST_NAME_COMPONENT = new RecordComponentWrapper(User.class,
>> "firstName");
>>   public static final RecordComponentWrapper<String>
>> LAST_NAME_COMPONENT = new RecordComponentWrapper(User.class,
>> "lastName");
>>   public static final RecordComponentWrapper<ComplexObject>
>> COMPLEX_COLUMN_COMPONENT = new RecordComponentWrapper(User.class,
>> "complexColumn");
>> }
>>
>> where `RecordComponentWrapper` is some suitable type-safe wrapper for
>> record components.
>>
>> Although there is a string for each record component, it is in
>> generated code, thus won't get out of sync.
>>
>> (This is the annotation processing equivalent to how Joda-Beans
>> meta-properties have worked for many years)
>> Stephen
>>
>> On Sun, 30 Nov 2025 at 20:39, David Alayachew <davidalayachew at gmail.com>
>> wrote:
>> >
>> > Thanks everyone for the context. The answers I am getting are very
>> helpful. I do see a comment about XY problem, so here is the full context
>> of my problem.
>> >
>> > Unlike normal classes, record are transparent data carriers. Which make
>> them useful for simple DTO's and serialization. Specifically, I wanted to
>> create a De/serialization format for a CSV file.
>> >
>> > Let's say I have record User(String firstName, String lastName,
>> ComplexObject complexColumn) {}, and each RecordComponent corrsponds to a
>> column of my CSV, while each instance of User corresponds to a single row.
>> Let's also assume that firstName and lastName are computationally cheap to
>> deserialize, but complexColumn is expensive (complex but necessary regex).
>> >
>> > Well, I want to create a CSV De/Serialization tool that allows me to
>> not only deserialize the object in full, but also deserialize only the
>> specific component I want. In this case, if all I care about is firstName
>> and lastName, then why should I pay the price of also deserializing
>> complexColumn, which I won't use at all?
>> >
>> > Hence why I requested these various things in the thread -- such as the
>> ability to reference RecordComponents, or why I wanted to use a method
>> reference to a records accessors. I wanted some way to get compile time
>> type safety about the column I am requesting. If I change my User data type
>> such that firstName now says first instead, I want all callers of my tool
>> to get compile time errors. But I don't see how to do it.
>> >
>> > I really do think that, if records claim to be transparent carriers of
>> data, then being able to isolate and point to a single RecordComponent in a
>> type safe way that will fail at compile time if I am out of sorts sounds
>> like a reasonable thing to want from a record.
>> >
>> >
>> > On Sun, Nov 30, 2025 at 3:04 PM Kirill Semyonkin <burnytc at gmail.com>
>> wrote:
>> >>
>> >> Hello David,
>> >>
>> >> I've seen some people (ab)use Serializable and SerializedLambda to get
>> names of the class and the method, with numerous examples like
>> https://stackoverflow.com/a/53397378/16208077. From there you can
>> traverse usual reflection with those strings to get the exact
>> RecordComponent or some other reflection object. But those strings come
>> from users doing method references, so you will get the behavior that you
>> wanted, at least to some extent. Although it slightly feels like an XY
>> problem.
>> >>
>> >> - Kirill Semyonkin
>> >>
>> >> вс, 30 нояб. 2025 г. в 22:57, Attila Kelemen <
>> attila.kelemen85 at gmail.com>:
>> >>>
>> >>> I guess, if your record type is fixed, then you can have a dummy
>> instance of that record with all fields being different (hopefully, no 3
>> boolean components :)), and then you can do the same hack as with the
>> interface + proxy combo.
>> >>>
>> >>> Attila Kelemen <attila.kelemen85 at gmail.com> ezt írta (időpont: 2025.
>> nov. 30., V, 20:51):
>> >>>>
>> >>>> I think that is impossible. What I did when I needed something
>> similar is that I used an interface instead of a record and built utilities
>> around it, because in that case you can imitate what you want.
>> >>>>
>> >>>> That is, consider that you have `interface User { String
>> firstName(); String lastName() }`. In this case, you could have methods
>> taking a `Function<User, T>` and then you can create an instance of `User`
>> via `Proxy.newProxyInstance` in which you record which method was called,
>> and pass the proxy instance to the `Function`. Assuming that someone passed
>> `User::firstName` for the `Function`, you can detect which method was
>> passed. The drawbacks are obvious, but it is better than passing strings in
>> my opinion.
>> >>>>
>> >>>> David Alayachew <davidalayachew at gmail.com> ezt írta (időpont: 2025.
>> nov. 30., V, 20:41):
>> >>>>>
>> >>>>> Thanks for the response Attila.
>> >>>>>
>> >>>>> Let me try and loosen the constraints then -- is there any way for
>> me to get a RecordComponent corresponding to firstName without needing to
>> do String comparison?
>> >>>>>
>> >>>>> At the end of the day, that's all that I really need.
>> >>>>>
>> >>>>> As is now, the only way I can see to get a RecordComponent is to
>> drill down from j.l.Class --> j.l.r.RecordComponent, then do String
>> comparison against a provided String to get the record component that I
>> want. That is unsafe and stringly typed, so, very much undesirable. After
>> all, if I change my record to say first instead of firstName, I want a
>> compiler error. But doing it that way, I won't -- I'll get a runtime error.
>> >>>>>
>> >>>>> So that's what I really want -- a type-safe way to isolate a record
>> component from a record, without forcing my users to have to provide a
>> String corresponding to the record component name.
>> >>>>>
>> >>>>> On Sun, Nov 30, 2025 at 2:16 PM Attila Kelemen <
>> attila.kelemen85 at gmail.com> wrote:
>> >>>>>>
>> >>>>>> I'm pretty sure there is no such thing, because that essentially
>> implies the existence of some kind of method literal (well, it would not be
>> strictly necessary, but the JLS would feel strange without it), and there
>> is no such thing (though it would be awesome, if there was).
>> >>>>>>
>> >>>>>> Also, note that if this was a thing, then your case is just a very
>> special case, and you would want more (I would for sure). That is, in that
>> case, I would also want type safety. So, something like
>> MethodReference<(MyRecord) -> String> (which of course would require
>> function types in Java).
>> >>>>>>
>> >>>>>> When I needed this, luckily I could restrain my need to interface
>> methods (as opposed to your record getters) where I could create a `Proxy`
>> and see which method gets called (nasty hack, has its downsides, but felt
>> like the safest to me).
>> >>>>>>
>> >>>>>> Attila
>> >>>>>>
>> >>>>>> David Alayachew <davidalayachew at gmail.com> ezt írta (időpont:
>> 2025. nov. 29., Szo, 20:50):
>> >>>>>>>
>> >>>>>>> And by all means, add more parameters to foo if you want. For
>> example, if a User.class helps, please do so!
>> >>>>>>>
>> >>>>>>> I just don't want to do anything like this.
>> >>>>>>>
>> >>>>>>> foo("firstName");
>> >>>>>>>
>> >>>>>>> That's stringly typed, and undesirable for my use case.
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20251201/5ba8747e/attachment-0001.htm>


More information about the amber-dev mailing list