Method Handles in Java
A method handle is a typed, directly executable reference to a method, constructor or field. Like a reflection Method object, it lets you call something chosen at run time. Unlike reflection, a method handle has an exact type, its access rights are checked once, when it is created, not on every call, and the JIT compiler can optimize a call through it almost like a normal method call. Method handles live in the package java.lang.invoke, added in Java 7. They are what the JVM itself uses to run lambdas, string concatenation and pattern-matching switches. In this lesson, you will learn how to create, call and combine method handles, and when to choose them over reflection.
The Three Key Classes
| Class | Purpose |
|---|---|
MethodHandles.Lookup | A factory for method handles. It carries the access rights of the class that created it. |
MethodType | Describes a signature: the return type followed by the parameter types. |
MethodHandle | The callable handle itself. |
Methods of Lookup
| Method | Description |
|---|---|
findVirtual(class, name, type) | It is used to get a handle to an instance method. |
findStatic(class, name, type) | It gets a handle to a static method. |
findConstructor(class, type) | It gets a handle to a constructor; the type's return type is void.class. |
findGetter(class, field, fieldType), findSetter(...) | They get handles that read or write a field. |
unreflect(method) | It converts a reflection Method into a handle. |
MethodHandles.privateLookupIn(class, lookup) | It returns a lookup with private access to another class, if modules allow it. |
Example: Creating and Calling Method Handles
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.WrongMethodTypeException;
public class Mh {
static class Student {
private final String name;
private int marks;
public Student(String name, int marks) {
this.name = name;
this.marks = marks;
}
public String report(String prefix) {
return prefix + name + " scored " + marks;
}
public static int maxMarks() {
return 100;
}
}
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle report = lookup.findVirtual(Student.class, "report",
MethodType.methodType(String.class, String.class));
MethodHandle max = lookup.findStatic(Student.class, "maxMarks",
MethodType.methodType(int.class));
MethodHandle create = lookup.findConstructor(Student.class,
MethodType.methodType(void.class, String.class, int.class));
MethodHandle getMarks =
lookup.findGetter(Student.class, "marks", int.class);
Student s = (Student) create.invoke("Navika", 91);
System.out.println(report.type());
System.out.println((String) report.invokeExact(s, "> "));
System.out.println((int) max.invokeExact());
System.out.println((int) getMarks.invoke(s));
MethodHandle bound = report.bindTo(s);
System.out.println(bound.type());
System.out.println(bound.invoke("* "));
try {
Object result = report.invokeExact(s, "> ");
} catch (WrongMethodTypeException e) {
System.out.println("WrongMethodTypeException: "
+ e.getMessage());
}
MethodHandle upper = lookup.findVirtual(String.class,
"toUpperCase", MethodType.methodType(String.class));
MethodHandle shout = MethodHandles.filterReturnValue(bound, upper);
System.out.println(shout.invoke("! "));
}
}(Student,String)String
> Navika scored 91
100
91
(String)String
* Navika scored 91
WrongMethodTypeException: handle's method type (Student,String)String but found (Student,String)Object
! NAVIKA SCORED 91Explanation:
MethodType.methodType(String.class, String.class)means "returnsString, takes oneString". The return type always comes first.report.type()prints(Student,String)String. For an instance method, the object it is called on becomes the first parameter.create.invoke("Navika", 91)calls the constructor; the handle's type is(String,int)Student.bindTo(s)fixes the first argument, giving a new handle of type(String)Stringthat always works ons.filterReturnValue(bound, upper)builds a new handle that callsboundand then passes the result throughtoUpperCase. Handles can be combined like building blocks;MethodHandleshas many such adapters (insertArguments,dropArguments,guardWithTestand more).maindeclaresthrows ThrowablebecauseinvokeandinvokeExactcan throw anything the target throws, unwrapped. There is noInvocationTargetException.
Invoke vs InvokeExact
| Point | invokeExact | invoke |
|---|---|---|
| Type matching | The call's types must match the handle's type exactly, including the return type | Converts arguments and the result when possible (boxing, widening, casting) |
| On a mismatch | WrongMethodTypeException | Converts, or throws if no conversion exists |
| Speed | Fastest | Slightly slower when conversions are needed |
The compiler works out the call's types from what you write: the argument types and the cast on the result. That is why (String) report.invokeExact(s, "> ") works, while Object result = report.invokeExact(s, "> ") fails: the second asks for a result of type Object, which isn't exactly String. If invokeExact is fussy, use invoke.
Access Is Checked When the Handle Is Created
A Lookup can find whatever the class that called MethodHandles.lookup() could access in normal code. The check happens in findVirtual and its siblings; after that, anyone holding the handle can call it with no further checks. A class can therefore create a handle to its own private method and pass it to code it trusts. To reach another class's private members, MethodHandles.privateLookupIn(target, lookup) gives a lookup with private access, subject to the same module rules as setAccessible.
Method Handles vs Reflection
| Point | Reflection (Method) | Method handle |
|---|---|---|
| Purpose | Inspecting and calling | Only calling |
| Typing | Arguments as Object..., result as Object | Exact MethodType |
| Access check | On every call (unless setAccessible(true)) | Once, at lookup |
| Exceptions from the target | Wrapped in InvocationTargetException | Thrown directly |
| Can list a class's members | Yes | No |
| Can be combined and adapted | No | Yes (bindTo, filterReturnValue, ...) |
| Speed | Good when the Method is cached | As fast as a direct call when held in a static final field |
How Fast Are They?
A JMH benchmark calling counter.add(n) five ways on JDK 25 gave these results (see "Benchmarking with JMH"):
Benchmark Mode Cnt Score Error Units
CallBench.constantMethodHandle avgt 5 0.601 ± 0.022 ns/op
CallBench.directCall avgt 5 0.602 ± 0.012 ns/op
CallBench.nonConstantMethodHandle avgt 5 1.983 ± 0.035 ns/op
CallBench.reflection avgt 5 0.941 ± 0.023 ns/op
CallBench.reflectionWithLookup avgt 5 10.221 ± 0.178 ns/opExplanation:
- A method handle in a
static finalfield is as fast as the direct call. The JIT knows the handle can never change, so it inlines the target. - A handle in an ordinary field is slower, because the JIT can't assume which method it points to.
- Cached reflection (the
Methodlooked up once) is close behind. Since Java 18,Method.invokeis itself implemented with method handles. - Looking the method up on every call is by far the slowest. The lookup, not the call, is the expensive part.
Your numbers will differ, but the order is typical.
Where the JVM Uses Method Handles
- Lambdas: the
invokedynamicinstruction links a lambda throughLambdaMetafactory, which receives method handles. - String concatenation with
+usesStringConcatFactory. - Pattern-matching switches use
SwitchBootstraps.typeSwitch. VarHandle, used for atomic field access, is the same idea for variables.
Common Mistakes
Using invokeExact without the right cast. The call's return type is part of the match.
Keeping handles in non-final fields. They still work, but lose most of their speed advantage.
Using method handles to inspect classes. They can't list members; use reflection for discovery, then unreflect if you need speed.
Forgetting the receiver. A handle from findVirtual takes the target object as its first argument.
Interview Corner
What is a MethodHandle and how is it different from reflection?
A MethodHandle (package java.lang.invoke, Java 7) is a typed, directly executable reference to a method, constructor or field. It is obtained from a MethodHandles.Lookup with a MethodType describing the signature. Differences from reflection: access is checked once at lookup time instead of on each call, the handle is strongly typed, target exceptions aren't wrapped, handles can be combined and adapted, and the JIT can inline a handle held in a static final field so it runs as fast as a direct call. Reflection, on the other hand, can inspect a class's members and annotations, which method handles can't.
What is the difference between invoke and invokeExact?
invokeExact requires the types at the call site (arguments and the cast return type) to match the handle's MethodType exactly, or it throws WrongMethodTypeException. invoke performs conversions such as boxing, unboxing, widening and casting to make the call fit.
Practice
- Easy: Create a method handle for
String.length()and call it on three strings. - Harder: Create a handle for
Math.max(int, int), useMethodHandles.insertArgumentsto fix the first argument at 0, and use the result to turn negative numbers into 0.
Key Takeaways
Was this tutorial helpful?