diff a/src/jdk.incubator.code/share/classes/jdk/incubator/code/dialect/java/impl/MethodRefImpl.java b/src/jdk.incubator.code/share/classes/jdk/incubator/code/dialect/java/impl/MethodRefImpl.java
--- /dev/null
+++ b/src/jdk.incubator.code/share/classes/jdk/incubator/code/dialect/java/impl/MethodRefImpl.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.code.dialect.java.impl;
+
+import jdk.incubator.code.dialect.java.JavaOp;
+import jdk.incubator.code.dialect.java.MethodRef;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandleInfo;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+import jdk.incubator.code.dialect.core.FunctionType;
+import jdk.incubator.code.dialect.java.JavaType;
+import jdk.incubator.code.TypeElement;
+import jdk.incubator.code.extern.ExternalizedTypeElement;
+
+import java.util.List;
+import java.util.function.Function;
+
+import static java.util.stream.Collectors.joining;
+
+public final class MethodRefImpl implements MethodRef {
+
+    final TypeElement refType;
+    final String name;
+    final FunctionType type;
+
+    public MethodRefImpl(TypeElement refType, String name, FunctionType type) {
+        this.refType = refType;
+        this.name = name;
+        this.type = type;
+    }
+
+    @Override
+    public TypeElement refType() {
+        return refType;
+    }
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    @Override
+    public FunctionType type() {
+        return type;
+    }
+
+    public Method resolveToDirectMethod(MethodHandles.Lookup l) throws ReflectiveOperationException {
+        return resolveToDirectHandle(l, hr -> hr.mhi().reflectAs(Method.class, l));
+    }
+
+    public MethodHandle resolveToDirectHandle(MethodHandles.Lookup l) throws ReflectiveOperationException {
+        return resolveToDirectHandle(l, HandleResult::mh);
+    }
+
+    <T> T resolveToDirectHandle(MethodHandles.Lookup l, Function<HandleResult, T> f) throws ReflectiveOperationException {
+        ReflectiveOperationException roe = null;
+        for (JavaOp.InvokeOp.InvokeKind ik :
+                List.of(JavaOp.InvokeOp.InvokeKind.STATIC, JavaOp.InvokeOp.InvokeKind.INSTANCE)) {
+            try {
+                HandleResult hr = resolveToHandleResult(l, ik);
+                if (hr.isDirect()) {
+                    return f.apply(hr);
+                }
+            } catch (NoSuchMethodException | IllegalAccessException e) {
+                roe = e;
+            }
+        }
+        if (roe == null) {
+            roe = new ReflectiveOperationException("Indirect reference to method");
+        }
+        throw roe;
+    }
+
+    @Override
+    public Method resolveToMethod(MethodHandles.Lookup l, JavaOp.InvokeOp.InvokeKind kind) throws ReflectiveOperationException {
+        MethodHandleInfo methodHandleInfo = l.revealDirect(resolveToHandle(l, kind));
+        return methodHandleInfo.reflectAs(Method.class, l);
+    }
+
+    @Override
+    public MethodHandle resolveToHandle(MethodHandles.Lookup l, JavaOp.InvokeOp.InvokeKind kind) throws ReflectiveOperationException {
+        Class<?> refC = resolve(l, refType);
+        MethodType mt = MethodRef.toNominalDescriptor(type).resolveConstantDesc(l);
+        return switch (kind) {
+            case SUPER -> l.findSpecial(refC, name, mt, l.lookupClass());
+            case STATIC -> l.findStatic(refC, name, mt);
+            case INSTANCE -> l.findVirtual(refC, name, mt);
+        };
+    }
+
+    record HandleResult (Class<?> refC, MethodHandle mh, MethodHandleInfo mhi) {
+        boolean isDirect() {
+            return refC == mhi.getDeclaringClass();
+        }
+    }
+
+    HandleResult resolveToHandleResult(MethodHandles.Lookup l, JavaOp.InvokeOp.InvokeKind kind) throws ReflectiveOperationException {
+        Class<?> refC = resolve(l, refType);
+        MethodType mt = MethodRef.toNominalDescriptor(type).resolveConstantDesc(l);
+        MethodHandle mh = switch (kind) {
+            case SUPER -> l.findSpecial(refC, name, mt, l.lookupClass());
+            case STATIC -> l.findStatic(refC, name, mt);
+            case INSTANCE -> l.findVirtual(refC, name, mt);
+        };
+        MethodHandleInfo mhi = l.revealDirect(resolveToHandle(l, kind));
+        return new HandleResult(refC, mh, mhi);
+    }
+
+    static Class<?> resolve(MethodHandles.Lookup l, TypeElement t) throws ReflectiveOperationException {
+        if (t instanceof JavaType jt) {
+            return (Class<?>)jt.erasure().resolve(l);
+        } else {
+            // @@@
+            throw new ReflectiveOperationException();
+        }
+    }
+
+    @Override
+    public ExternalizedTypeElement externalize() {
+        return JavaTypeUtils.methodRef(name, refType.externalize(),
+                type.returnType().externalize(),
+                type.parameterTypes().stream().map(TypeElement::externalize).toList());
+    }
+
+    @Override
+    public String toString() {
+        return JavaTypeUtils.toExternalRefString(externalize());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        MethodRefImpl that = (MethodRefImpl) o;
+
+        if (!refType.equals(that.refType)) return false;
+        if (!name.equals(that.name)) return false;
+        return type.equals(that.type);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = refType.hashCode();
+        result = 31 * result + name.hashCode();
+        result = 31 * result + type.hashCode();
+        return result;
+    }
+}
