Java MethodHandles: JVM-Native Method Invocation

MethodHandles, introduced in Java 7, provide a lower-level, faster alternative to java.lang.reflect. A MethodHandle is a typed, directly executable reference to a method, constructor, or field: it can be inlined by the JIT compiler in ways that reflection cannot. MethodHandles are the backbone of invokedynamic, lambdas, and VarHandle internally, and they're essential for framework authors and anyone building high-performance reflective code.

Contents

Lookup and Finding Methods

Method handles are obtained from a MethodHandles.Lookup object, which carries the access rights of the class where it was created. Access control is checked once at lookup time, not on every invocation.

Java
import java.lang.invoke.*;

// Lookup has the access rights of the calling class
MethodHandles.Lookup lookup = MethodHandles.lookup();

// findVirtual — instance methods (dispatch based on receiver type)
MethodHandle toUpperCase = lookup.findVirtual(
    String.class, "toUpperCase", MethodType.methodType(String.class));
// MethodType: (return type, param types...)
// For instance methods, receiver type is the first argument at invocation

// findStatic — static methods
MethodHandle parseInt = lookup.findStatic(
    Integer.class, "parseInt", MethodType.methodType(int.class, String.class));

// findConstructor — constructors
MethodHandle newStringBuilder = lookup.findConstructor(
    StringBuilder.class, MethodType.methodType(void.class, String.class));

// findGetter / findSetter — field access (same as get/set field in reflection)
MethodHandle nameGetter = lookup.findGetter(Person.class, "name", String.class);
MethodHandle nameSetter = lookup.findSetter(Person.class, "name", String.class);

// findSpecial — super method calls (like invokespecial bytecode)
// Only accessible from the class doing the call
MethodHandle superToString = lookup.findSpecial(
    Object.class, "toString",
    MethodType.methodType(String.class), MyClass.class);

Invoking: invokeExact vs invoke

There are two invocation styles with very different type-checking semantics:

Java
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle toUpperCase = lookup.findVirtual(
    String.class, "toUpperCase", MethodType.methodType(String.class));

// invokeExact — types must match EXACTLY (fastest, JIT-inlineable)
String result = (String) toUpperCase.invokeExact("hello");
// ^ Works: receiver is String, return cast to String

// invokeExact will throw WrongMethodTypeException if types don't match:
// Object result2 = (Object) toUpperCase.invokeExact("hello"); // WRONG
// Must be: String result2 = (String) toUpperCase.invokeExact("hello");

// invoke — allows auto-conversion (slower, more flexible)
Object result3 = toUpperCase.invoke("hello");      // auto-unboxes/casts
String result4 = (String) toUpperCase.invoke("hello");

// invokeWithArguments — takes a List or varargs (most flexible, slowest)
String result5 = (String) toUpperCase.invokeWithArguments(List.of("hello"));

// Calling a static method handle
MethodHandle parseInt = lookup.findStatic(
    Integer.class, "parseInt", MethodType.methodType(int.class, String.class));
int n = (int) parseInt.invokeExact("42"); // exactly matches the MethodType

// Calling a constructor
MethodHandle newSB = lookup.findConstructor(
    StringBuilder.class, MethodType.methodType(void.class, String.class));
StringBuilder sb = (StringBuilder) newSB.invoke("hello");
System.out.println(sb.toString()); // hello
Prefer invokeExact in hot code paths: it is the fastest invocation and can be fully inlined by the JIT. Use invoke or invokeWithArguments when types are not statically known or for convenience in less-critical code.

Method Handle Transformations

MethodHandles provides combinator methods to adapt and transform handles:

Java
MethodHandles.Lookup lookup = MethodHandles.lookup();

// bindTo — partially apply (bind receiver or first argument)
MethodHandle toUpperCase = lookup.findVirtual(
    String.class, "toUpperCase", MethodType.methodType(String.class));
MethodHandle helloUpper = toUpperCase.bindTo("hello");
// Now takes no arguments, always returns "HELLO"
String s = (String) helloUpper.invokeExact(); // "HELLO"

// asType — adapt the MethodType (with conversions)
MethodHandle parseInt = lookup.findStatic(
    Integer.class, "parseInt", MethodType.methodType(int.class, String.class));
MethodHandle parseIntObj = parseInt.asType(
    MethodType.methodType(Object.class, Object.class));
// Now accepts Object and returns Object (with auto-boxing/casting)

// dropArguments — insert ignored arguments at given positions
MethodHandle upper = lookup.findVirtual(String.class, "toUpperCase",
    MethodType.methodType(String.class));
MethodHandle upperIgnoreInt = MethodHandles.dropArguments(
    upper, 1, int.class); // now: (String, int) → String (ignores the int)

// insertArguments — partially apply by inserting fixed arguments
MethodHandle substring = lookup.findVirtual(
    String.class, "substring", MethodType.methodType(String.class, int.class, int.class));
MethodHandle sub0to3 = MethodHandles.insertArguments(substring, 1, 0, 3);
// now: (String) → String, always calls substring(0, 3)
String prefix = (String) sub0to3.invokeExact("hello world"); // "hel"

// filterArguments — apply a handle to transform arguments before passing
MethodHandle trimmer = lookup.findVirtual(String.class, "trim",
    MethodType.methodType(String.class));
MethodHandle filteredUpper = MethodHandles.filterArguments(upper, 0, trimmer);
// trimmer.invoke(s) is called first, then upper.invoke(trimmed)
String r = (String) filteredUpper.invokeExact("  hello  "); // "HELLO"

// guardWithTest — conditional dispatch (if pred then trueHandle else falseHandle)
MethodHandle isNull = lookup.findStatic(
    Objects.class, "isNull", MethodType.methodType(boolean.class, Object.class));
// MethodHandles.guardWithTest(test, trueHandle, falseHandle)

Unreflect: From java.lang.reflect

You can wrap existing java.lang.reflect objects into method handles using unreflect methods. This is useful when you already have a Method, Field, or Constructor from reflection:

Java
// unreflect — wrap a java.lang.reflect.Method
Method method = String.class.getMethod("toUpperCase");
MethodHandle mh = MethodHandles.lookup().unreflect(method);
String result = (String) mh.invoke("hello"); // "HELLO"

// unreflectConstructor — wrap a Constructor
Constructor<ArrayList> ctor = ArrayList.class.getConstructor(int.class);
MethodHandle newList = MethodHandles.lookup().unreflectConstructor(ctor);
List<String> list = (List<String>) newList.invoke(16); // new ArrayList(16)

// unreflectGetter / unreflectSetter — wrap a Field
Field nameField = Person.class.getDeclaredField("name");
nameField.setAccessible(true);
MethodHandle getter = MethodHandles.lookup().unreflectGetter(nameField);
MethodHandle setter = MethodHandles.lookup().unreflectSetter(nameField);

Person p = new Person("Alice");
String name = (String) getter.invoke(p);    // "Alice"
setter.invoke(p, "Bob");                    // sets name to "Bob"

// privateLookupIn — access private members of another class (Java 9+)
// Requires the other class to open its package to your module
MethodHandles.Lookup privateLookup =
    MethodHandles.privateLookupIn(OtherClass.class, MethodHandles.lookup());
MethodHandle privateMethod = privateLookup.findVirtual(
    OtherClass.class, "privateMethod", MethodType.methodType(void.class));

Practical Use Cases

MethodHandles are preferred over reflection in performance-critical code because the JIT compiler can inline and optimize them in ways that Method.invoke() cannot. They are the ideal foundation for implementing dynamic dispatch, building lookup tables of handlers keyed by type at startup, then invoking them on the hot path with near-zero overhead. Internally they power many core Java mechanisms: invokedynamic byte code, the lambda metafactory, and framework internals such as method routers and property accessors.

Java
// Use case 1: High-performance event dispatcher
// Build a map of method handles indexed by event type at startup
Map<Class<?>, MethodHandle> handlers = new HashMap<>();
MethodHandles.Lookup lookup = MethodHandles.lookup();

// Register handlers by scanning annotations (once, at startup)
for (Method m : EventHandler.class.getMethods()) {
    if (m.isAnnotationPresent(Handle.class)) {
        Class<?> eventType = m.getParameterTypes()[0];
        handlers.put(eventType, lookup.unreflect(m)
            .bindTo(new EventHandler())); // bind receiver
    }
}

// Dispatch (hot path — invokeExact is JIT-inlined)
void dispatch(Object event) throws Throwable {
    MethodHandle handler = handlers.get(event.getClass());
    if (handler != null) handler.invoke(event);
}

// Use case 2: Generic accessor without reflection overhead
class BeanAccessor {
    private final MethodHandle getter;
    private final MethodHandle setter;

    BeanAccessor(Class<?> beanClass, String fieldName, Class<?> fieldType)
            throws ReflectiveOperationException {
        var lookup = MethodHandles.lookup();
        this.getter = lookup.findGetter(beanClass, fieldName, fieldType);
        this.setter = lookup.findSetter(beanClass, fieldName, fieldType);
    }

    Object get(Object bean) throws Throwable { return getter.invoke(bean); }
    void set(Object bean, Object value) throws Throwable { setter.invoke(bean, value); }
}
Method handles checked at lookup time but types verified at invocation: a WrongMethodTypeException (unchecked) is thrown at runtime if types don't match. Always store handles in static final fields and compute them once during class initialization for maximum JIT optimization.