Valhalla breaks minimal-j framework

Ethan McCue ethan at mccue.dev
Tue Dec 2 15:35:44 UTC 2025


        var form = new Form<Person>();
        form.line(() -> $.number);
        form.line(() -> $.name);
        form.line(() -> $.address.city);


        Backend.find(Person.class, By.field(() -> $.name, "Bruno"));
        Backend.find(Person.class, By.field(() -> $.address.zip, 8000));

This might be all that is needed - forgiving the perf difference, general
hackiness, etc.

On Mon, Dec 1, 2025 at 11:50 PM Ethan McCue <ethan at mccue.dev> wrote:

> There is a parallel thread going on which touches on similar things -
> there people are wanting "record component references."
>
> I adapted the code from
> https://github.com/rilindbicaj/fluentmapper/blob/main/fluentmapper-provider/src/main/java/dev/bici/fluentmapper/provider/expression/parser/ExpressionParser.java
> to use the new classfile api + work for a quick demo of the approach.
>
> Basically, you can get at the code in the body of a lambda via some
> serialization hackery. The degree to which this is actually supported is to
> me unknown.
>
> import java.io.IOException;
> import java.io.Serializable;
> import java.io.UncheckedIOException;
> import java.lang.classfile.ClassFile;
> import java.lang.classfile.ClassModel;
> import java.lang.classfile.instruction.FieldInstruction;
> import java.lang.invoke.SerializedLambda;
> import java.lang.reflect.Field;
> import java.util.ArrayList;
> import java.util.List;
>
> public final class FieldReference {
>     private final Class<?> root;
>     private final List<Field> fields;
>
>     private FieldReference(Class<?> root, List<Field> fields) {
>         this.root = root;
>         this.fields = fields;
>     }
>
>
>     public Class<?> root() {
>         return root;
>     }
>
>     public List<Field> fields() {
>         return fields;
>     }
>
>     private static SerializedLambda toSerializedLambda(Expression<?, ?>
> expression) {
>         try {
>             var writeReplaceMethod =
> expression.getClass().getDeclaredMethod("writeReplace");
>             writeReplaceMethod.setAccessible(true);
>
>             return (SerializedLambda)
> writeReplaceMethod.invoke(expression);
>         } catch (ReflectiveOperationException e) {
>             throw new IllegalArgumentException("Could not extract
> SerializedLambda from the provided expression;", e);
>         }
>     }
>
>     public static <T> FieldReference of(Class<T> klass, Expression<T, ?>
> expression) {
>         // TODO: This work can likely be cached.
>         var serializedLambda = toSerializedLambda(expression);
>         var containingClass = serializedLambda.getImplClass();
>         var lambdaSignature = serializedLambda.getImplMethodName();
>
>         // TODO: Unsure if this is the right class loader to use
>         var classLoader = klass.getClassLoader();
>
>         byte[] classBytes;
>         try (var classResource = classLoader.getResourceAsStream(
>                 containingClass.replace('.', '/') + ".class"
>         )) {
>             if (classResource == null) {
>                 throw new IllegalArgumentException("Could not find
> class-file resource in class loader");
>             }
>
>             classBytes = classResource.readAllBytes();
>         } catch (IOException e) {
>             throw new UncheckedIOException(e);
>         }
>
>         ClassModel classModel = ClassFile.of().parse(classBytes);
>
>         var methodModel = classModel.methods().stream()
>                 .filter(method ->
> lambdaSignature.equals(method.methodName().toString()))
>                 .findFirst()
>                 .orElseThrow(() -> new IllegalArgumentException("Could not
> find the implementing method for the expression lambda"));
>         var code = methodModel.code().orElseThrow();
>
>         // TODO: This could use a lot of prechecks
>         var fields = new ArrayList<Field>();
>
>         Class<?> lastType = klass;
>         for (var element : code.elementList()) {
>             if (element instanceof FieldInstruction fieldInstruction) {
>                 try {
>                     fields.add(
>
> lastType.getDeclaredField(fieldInstruction.field().name().toString())
>                     );
>
>
>                     // TODO: This is almost certainly not sound
>                     if
> (fieldInstruction.typeSymbol().packageName().isEmpty()) {
>                         lastType = classLoader.loadClass(
>                                 fieldInstruction.typeSymbol().displayName()
>                         );
>                     }
>                     else {
>                         lastType = classLoader.loadClass(
>
> fieldInstruction.typeSymbol().packageName() + "."
>                                         +
> fieldInstruction.typeSymbol().displayName()
>                         );
>                     }
>
>                 } catch (NoSuchFieldException e) {
>                     throw new IllegalArgumentException("Inaccessable
> field", e);
>                 } catch (ClassNotFoundException e) {
>                     throw new RuntimeException(e);
>                 }
>             }
>         }
>
>
>         return new FieldReference(klass, List.copyOf(fields));
>     }
>
>
>     @FunctionalInterface
>     public interface Expression<A, B> extends Serializable {
>         B get(A a);
>     }
>
>
>     @Override
>     public String toString() {
>         return "FieldReference[" +
>                 "root=" + root.getName() +
>                 ", fields=" + fields.stream().map(Field::getName).toList()
> +
>                 ']';
>     }
> }
>
> And you can use all of that with a lambda that only accesses fields to
> emulate "field references."
>
> class A {
>     String aa;
>     B b;
> }
>
> class B {
>     C c;
> }
>
>  class C {
>     String v;
>     D d;
> }
>
>  class D {
>     E e;
> }
>
>  class E {
>     F f;
> }
>
>  class F {
>     G g;
> }
>
>  class G {
>     String v;
> }
>
> public class Test {
>     public static void main(String[] args) {
>         var refA = FieldReference.of(A.class, a -> a.aa);
>         IO.println(refA);
>         var refB = FieldReference.of(A.class, a -> a.b.c.d);
>         IO.println(refB);
>     }
> }
>
> FieldReference[root=A, fields=[aa]]
> FieldReference[root=A, fields=[b, c, d]]
>
> So that is one approach to look into.
>
>
> On Mon, Dec 1, 2025, 8:38 PM Bruno Eberhard <bruno.eberhard at pop.ch> wrote:
>
>> Am 01.12.2025 um 22:00 schrieb Ethan McCue:
>> > On that note, i'd say you have somewhat of a bigger problem w.r.t. enums
>> > [..]
>> > As with all things in jdk.unsupported, I can't help but wonder if there
>> > are any VM level invariants you run afoul of by making extra enum
>> > instances.
>>
>> I don't know about the VM internals. I did never run into problems with
>> these extra enums.
>>
>> > My first stab at #2 is to add something like this record [..]
>> Thank you for your input. I have to try this.
>>
>> > I think it's also worth asking - do you have any usage statistics on
>> > your library? (Maven central used to offer download stats, etc.) If you
>> > are going to be broken anyways it is probably useful to know the blast
>> > radius.
>>
>> Very good point. As far as I know minimal-j is only used by me for a
>> smaller and one bigger App. In the bigger App ( An ERP called
>> lisheane.ch , currently only available in german ) I invested quite some
>> time. I would have to rewrite parts of it.
>>
>> So the world will not be standing still if minimal-j doesn't work
>> anymore or has to be changed.
>>
>> I would still like the idea of field references ( Person::name ,
>> Person::address::city ) in the java language as equivalent for the $
>> constant. It is really nice to use. But I don't have the knowledge how
>> to add it to the language or how to propose the feature.
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/valhalla-dev/attachments/20251202/fd5c1417/attachment-0001.htm>


More information about the valhalla-dev mailing list