🪞 Reflection, Annotations & Modules · Advanced

MethodHandles in Java

A faster, typed alternative to reflection.

🧩 The mysteryReflection checks access on every single call. What if you could check once, and then call a method dynamically almost as fast as a normal call?

A typed method reference

A **MethodHandle** (from java.lang.invoke) is a typed, directly executable reference to a method, constructor or field. You get one from a **Lookup, describing the signature with a MethodType** (return type first, then parameters).

MethodHandle len = MethodHandles.lookup()
    .findVirtual(String.class, "length",
        MethodType.methodType(int.class));
int n = (int) len.invokeExact("abc"); // 3

Check once, call fast

A Lookup checks access once, at lookup time, while Method.invoke checks on every call. Store a handle in a **static final field and the JIT treats it as a constant**: it can inline the target just like a regular call.

🔮 Predict it

Repeat after me

What does this print?

void main() throws Throwable {
    var t = MethodType.methodType(
        String.class, int.class);
    MethodHandle rep = MethodHandles.lookup()
        .findVirtual(String.class, "repeat", t);
    String s = (String) rep.invokeExact("ab", 3);
    System.out.println(s);
}
  1. ababab
  2. ab3
  3. Throws WrongMethodTypeException
Show the answer

findVirtual finds String.repeat(int), type (int)String. invokeExact passes "ab" as the receiver and 3 as the argument, and the call site types match exactly.

invokeExact is picky

With **invokeExact, the types at the call site (including the cast on the result) must match the handle's type exactly, or you get WrongMethodTypeException. invoke** is more relaxed: it adapts types with boxing and casts.

Calling a (String)int handle

✗ Mismatch
Object n = len.invokeExact("abc");

Call site is (String)Object, so WrongMethodTypeException.

✓ Exact
int n = (int) len.invokeExact("abc");

Call site is (String)int. Or use invoke(), which adapts.

🔮 Predict it

Close isn't exact

len has type (String)int. What happens here?

void main() throws Throwable {
    MethodHandle len = MethodHandles.lookup()
        .findVirtual(String.class, "length",
            MethodType.methodType(int.class));
    long n = (long) len.invokeExact("abc");
    System.out.println(n);
}
  1. Prints 3
  2. Throws WrongMethodTypeException
  3. Compile error
Show the answer

The (long) cast makes the call site type (String)long. int widens to long in normal code, but **invokeExact refuses any mismatch**.

The invoke family

**MethodHandle: typed reference to a method. VarHandle: typed access to a field or array element, with atomic access modes. MethodType: describes return and parameter types. Lookup**: the factory that checks access rights.

💼 In the real world

Under the hood of Java

Method handles are everywhere you don't look: invokedynamic call sites for lambdas and string concatenation are linked to method handles, and since JDK 18 core reflection itself is built on method handles. High-performance libraries use them for fast dynamic calls.

Key takeaways

  1. Lookup checks access once, not on every call
  2. MethodType describes return and parameter types
  3. invokeExact: exact match, or WrongMethodTypeException
  4. Core reflection is built on method handles since JDK 18
🤯 Did you know?

invokeExact and invoke are "signature polymorphic": javac records the exact argument and return types at each call site, which is why a simple cast changes the call's type.

Practice questions

What does this print?

void main() throws Throwable {
    MethodHandle up = MethodHandles.lookup()
        .findVirtual(String.class, "toUpperCase",
            MethodType.methodType(String.class));
    String s = (String) up.invokeExact("hi");
    System.out.println(s);
}
  1. HI
  2. Throws WrongMethodTypeException
  3. toUpperCase
  4. hi
Check your answer

HI. findVirtual finds the instance method String.toUpperCase() with type ()String. invokeExact passes "hi" as the receiver and the call site type matches exactly.

What does this print?

void main() throws Throwable {
    MethodHandle len = MethodHandles.lookup()
        .findVirtual(String.class, "length",
            MethodType.methodType(int.class));
    Object n = len.invokeExact("abc");
    System.out.println(n);
}
  1. Compile error
  2. Throws WrongMethodTypeException
  3. Throws ClassCastException
  4. 3
Check your answer

Throws WrongMethodTypeException. The handle's type is (String)int, but the call site asks for (String)Object because the result is assigned to Object. invokeExact refuses any mismatch.

Reflection can reach into almost anything... unless a module says no. Next: the Java module system.