MethodHandles in Java
A faster, typed alternative to reflection.
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"); // 3Check 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.
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);
}abababab3Throws 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
Object n = len.invokeExact("abc");Call site is (String)Object, so WrongMethodTypeException.
int n = (int) len.invokeExact("abc");Call site is (String)int. Or use invoke(), which adapts.
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);
}Prints 3Throws WrongMethodTypeExceptionCompile 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.
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
- Lookup checks access once, not on every call
- MethodType describes return and parameter types
- invokeExact: exact match, or WrongMethodTypeException
- Core reflection is built on method handles since JDK 18
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);
}- HI
- Throws WrongMethodTypeException
- toUpperCase
- 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);
}- Compile error
- Throws WrongMethodTypeException
- Throws ClassCastException
- 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.