Method Handles in Java

Naveen AnamaneniUpdated · 8 min read

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

ClassPurpose
MethodHandles.LookupA factory for method handles. It carries the access rights of the class that created it.
MethodTypeDescribes a signature: the return type followed by the parameter types.
MethodHandleThe callable handle itself.

Methods of Lookup

MethodDescription
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

Mh.java▶ Try it
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("! "));
    }
}
Output
(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 91

Explanation:

  • MethodType.methodType(String.class, String.class) means "returns String, takes one String". 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)String that always works on s.
  • filterReturnValue(bound, upper) builds a new handle that calls bound and then passes the result through toUpperCase. Handles can be combined like building blocks; MethodHandles has many such adapters (insertArguments, dropArguments, guardWithTest and more).
  • main declares throws Throwable because invoke and invokeExact can throw anything the target throws, unwrapped. There is no InvocationTargetException.

Invoke vs InvokeExact

PointinvokeExactinvoke
Type matchingThe call's types must match the handle's type exactly, including the return typeConverts arguments and the result when possible (boxing, widening, casting)
On a mismatchWrongMethodTypeExceptionConverts, or throws if no conversion exists
SpeedFastestSlightly 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

PointReflection (Method)Method handle
PurposeInspecting and callingOnly calling
TypingArguments as Object..., result as ObjectExact MethodType
Access checkOn every call (unless setAccessible(true))Once, at lookup
Exceptions from the targetWrapped in InvocationTargetExceptionThrown directly
Can list a class's membersYesNo
Can be combined and adaptedNoYes (bindTo, filterReturnValue, ...)
SpeedGood when the Method is cachedAs 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"):

Terminal output
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/op

Explanation:

  • A method handle in a static final field 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 Method looked up once) is close behind. Since Java 18, Method.invoke is 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 invokedynamic instruction links a lambda through LambdaMetafactory, which receives method handles.
  • String concatenation with + uses StringConcatFactory.
  • 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

  1. Easy: Create a method handle for String.length() and call it on three strings.
  2. Harder: Create a handle for Math.max(int, int), use MethodHandles.insertArguments to fix the first argument at 0, and use the result to turn negative numbers into 0.

Quick check: Method handles

4 questions
1In MethodType.methodType(String.class, int.class), what is String?
2When are access rights checked for a method handle?
3Why does Object r = handle.invokeExact(s, "x") fail for a handle returning String?
4How do you get the best performance from a method handle?

Key Takeaways

Was this tutorial helpful?

Lesson 470 of 482