skip to content
everything.meta.methodHandlesAndVarhandles

method handles and varhandles

fast, typed references to methods and fields.

chapter
chapter 11.05
reading time
9 min read

a MethodHandle is a typed, directly callable reference to a method, a constructor or a field access. you get one from a Lookup, which checks access once, when you find it. calling it with invokeExact can be as fast as a normal call. a VarHandle is the same idea for variables, plus a menu of memory-ordering modes and atomic operations like compareAndSet.

they arrived in java 7 and 9, and today they're the engine under reflection, lambdas, string concatenation and most of java.util.concurrent.

the picture

reflection is calling the front desk every time: "is this caller allowed to use method bark? what's its signature? fine, boxing your arguments, connecting you." a method handle is a speed-dial button. the front desk checks you're allowed when the button gets programmed (the lookup), and after that, pressing it is just a call. you can also wire buttons together into macros (combinators), and the result is still one button.

lookup, find, invoke

Main.java
import java.lang.invoke.*;
 
void main() throws Throwable {
    MethodHandles.Lookup lookup = MethodHandles.lookup();
 
    MethodHandle repeat = lookup.findVirtual(String.class, "repeat",
        MethodType.methodType(String.class, int.class));
    MethodHandle max = lookup.findStatic(Math.class, "max",
        MethodType.methodType(int.class, int.class, int.class));
 
    String s = (String) repeat.invokeExact("ab", 3);
    int m = (int) max.invokeExact(4, 9);
    IO.println(s + " " + m);
    IO.println(repeat.type());
 
    Object loose = max.invoke((Integer) 4, (short) 9);
    IO.println(loose);
 
    try {
        long wrong = (long) max.invokeExact(4, 9);
    } catch (WrongMethodTypeException e) {
        IO.println(e.getMessage());
    }
}
output, from jdk 25
ababab 9
(String,int)String
9
handle's method type (int,int)int but found (int,int)long

this one needs a real JVM (reflection), so it doesn't run in the browser. copy it into java Main.java to try it.

the pieces:

  • MethodHandles.lookup() returns a Lookup carrying your class's access rights. if your code could call a private method directly, this lookup can find it; if not, findVirtual throws IllegalAccessException right there, at lookup time. there's no setAccessible (for another module's privates you need privateLookupIn, and the package has to be opened to you).
  • MethodType is the signature: return type first, then parameters. repeat is an instance method, so its handle's type is (String,int)String: the receiver becomes parameter zero.
  • findVirtual, findStatic, findConstructor, findGetter, findSpecial, and unreflect to convert a reflective Method you already have.

and then the strange part. invokeExact is declared as invokeExact(Object...), but javac doesn't compile it like a varargs call. it writes the types at your call site into the bytecode:

javap -c Main
53: invokevirtual #49  // Method java/lang/invoke/MethodHandle.invokeExact:(Ljava/lang/String;I)Ljava/lang/String;
62: invokevirtual #55  // Method java/lang/invoke/MethodHandle.invokeExact:(II)I
93: invokevirtual #75  // Method java/lang/invoke/MethodHandle.invoke:(Ljava/lang/Integer;S)Ljava/lang/Object;
107: invokevirtual #79 // Method java/lang/invoke/MethodHandle.invokeExact:(II)J

that's a signature polymorphic method: every call site gets its own descriptor, built from the argument types and the cast on the result. invokeExact then demands that descriptor equal the handle's type, exactly. no boxing, no widening, no array. that's why it can be fast, and why the last call fails: the cast to long made the call site (II)J, and the handle is (int,int)int.

invoke is the forgiving version: it adapts the call (unboxing the Integer, widening the short) with asType, at some cost.

predict the output
java
MethodHandle max = lookup.findStatic(Math.class, "max",
    MethodType.methodType(int.class, int.class, int.class));
Object r = max.invokeExact(4, 9);

building handles out of handles

MethodHandles has a toolbox of combinators that take handles and return new handles: bind an argument, drop one, reorder them, filter a result, add a try/catch, add an if, even build loops. the result is one handle you can call, pass around, or hand to the JIT.

)
size.invokeExact("ada") →10

one more filter, String.length, and the return type changes to int. the whole chain is a single MethodHandle that the JIT can inline end to end, when it's a constant.

type a name, then click a stage. each stage is a new handle built from the one above; the purple type on the right is what type() returns for it. the last one turned a two-argument concat into (String)int.
Main.java
import java.lang.invoke.*;
import static java.lang.invoke.MethodType.methodType;
 
void main() throws Throwable {
    var lookup = MethodHandles.lookup();
    MethodHandle concat = lookup.findVirtual(String.class, "concat", methodType(String.class, String.class));
    MethodHandle upper = lookup.findVirtual(String.class, "toUpperCase", methodType(String.class));
    MethodHandle length = lookup.findVirtual(String.class, "length", methodType(int.class));
 
    MethodHandle hello = MethodHandles.insertArguments(concat, 0, "hello, ");   // (String)String
    MethodHandle shout = MethodHandles.filterReturnValue(hello, upper);        // (String)String
    MethodHandle size = MethodHandles.filterReturnValue(shout, length);       // (String)int
 
    IO.println((String) shout.invokeExact("ada"));
    IO.println((int) size.invokeExact("ada"));
    IO.println(size.type());
}
output, from jdk 25
HELLO, ADA
10
(String)int

this one needs a real JVM (reflection), so it doesn't run in the browser. copy it into java Main.java to try it.

this is how language runtimes on the JVM (JRuby, Groovy, Nashorn back when it shipped) build their dispatch logic, and how invokedynamic call sites get wired: a bootstrap method assembles a handle out of pieces like these, and the JVM calls it.

how fast

same benchmark as the reflection chapter: t.add(x), measured with JMH on jdk 25, apple M4 Pro.

direct call0.711 ±0.03 ns/op
MethodHandle, static final0.707 ±0.03 ns/op
Method.invoke, static final1.592 ±0.009 ns/op
MethodHandle, plain field3.099 ±0.043 ns/op
Method.invoke, plain field5.565 ±0.051 ns/op
a handle in a static final field is a constant to the JIT, and it inlines straight through: the same speed as a direct call. in an ordinary field, it's an opaque target and costs a few nanoseconds.

so the rule for handles is the same as for reflection, but the payoff is bigger: look it up once, store it in a static final, call invokeExact. that's exactly what the JDK does internally (and since java 18, Method.invoke itself is implemented with method handles, JEP 416).

VarHandles: a handle to a variable

a VarHandle points at a field, an array element, or a spot in a byte buffer, and offers every way of accessing it: plain reads and writes, ordered ones, and atomic read-modify-write.

Main.java
import java.lang.invoke.*;
 
class Counter {
    int count;
    static final VarHandle COUNT;
    static {
        try {
            COUNT = MethodHandles.lookup().findVarHandle(Counter.class, "count", int.class);
        } catch (ReflectiveOperationException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
}
 
void main() {
    Counter c = new Counter();
    Counter.COUNT.set(c, 41);
    boolean won = Counter.COUNT.compareAndSet(c, 41, 42);
    boolean lost = Counter.COUNT.compareAndSet(c, 41, 99);
    IO.println(won + " " + lost + " " + c.count);
    int before = (int) Counter.COUNT.getAndAdd(c, 8);
    IO.println(before + " → " + (int) Counter.COUNT.getVolatile(c));
    IO.println(Counter.COUNT.varType() + " " + Counter.COUNT.coordinateTypes());
    Counter.COUNT.setRelease(c, 7);
    IO.println((int) Counter.COUNT.getAcquire(c));
}
output, from jdk 25
true false 42
42 → 50
int [class Main$Counter]
7

this one needs a real JVM (reflection), so it doesn't run in the browser. copy it into java Main.java to try it.

the field is a plain int, not an AtomicInteger, and yet you got a real CAS on it. the "coordinates" are what locates the variable (here, which Counter), and like invokeExact, the access methods are signature polymorphic, hence the (int) casts.

that's the point of VarHandles: atomic operations on ordinary fields, without allocating a wrapper object per field and without sun.misc.Unsafe, which is what libraries reached for before java 9. FutureTask keeps its state in three VarHandles (javap -p shows STATE, RUNNER and WAITERS); the very core, like AtomicInteger and ConcurrentHashMap, still calls the JDK's own internal Unsafe, which your code can't touch. there are also MethodHandles.arrayElementVarHandle(int[].class) for array slots and byteArrayViewVarHandle to read an int or long out of a byte[] at any offset, in either byte order.

the access modes

here's what's genuinely new compared to volatile fields: you pick the memory ordering per access.

↑ stronger, and potentially slower. each rung keeps everything below it.

acquire / release adds causality: everything the writer did before setRelease is visible to a reader whose getAcquire sees that write. enough to hand a message from one thread to another.

message passing: data is a plain field
T1
data = 42;
READY.setRelease(this, true);
T2
while (!(boolean) READY.getAcquire(this)) {}
int r = data;
r == 0? impossible. seeing ready = true means seeing data = 42.
store buffering: x and y start at 0
T1
X.setRelease(this, 1);
int r1 = (int) Y.getAcquire(this);
T2
Y.setRelease(this, 1);
int r2 = (int) X.getAcquire(this);
r1 == 0 && r2 == 0? allowed. each write can sit in a store buffer while the other thread reads.

use it for: single-producer queues, publishing an object. AtomicXxx.lazySet is setRelease.

pick a mode. each rung keeps the guarantees of the ones below it. the two tests are the classic litmus tests; red means the java memory model allows the surprising outcome, green means it can't happen.

almost all code should use volatile semantics (a volatile field, an AtomicXxx, or getVolatile/setVolatile/compareAndSet). the weaker modes exist for the people writing lock-free queues and concurrency libraries, who need to shave off the fences that volatile implies in a hot loop. on x86 even that saving is small: every mode except a volatile write compiles to an ordinary memory instruction there.

one level deeper:what the JIT sees+

a method handle is an object, but its behaviour is described by a small internal graph of "lambda forms" that the JVM can compile. when the handle is a compile-time constant (a static final field, or the target of a linked invokedynamic), C2 can look through the whole graph, inline the target method, and the handle disappears from the generated code. combinators fold away the same way, so filterReturnValue(shout, length) becomes a straight-line concat, toUpperCase, length. when the handle isn't a constant, the JVM has to go through an invokeBasic on whatever handle it finds at runtime, which can't be inlined, and that's the 3 ns row in the chart.

keylines
  • a Lookup checks access once. the handle it returns is just a call after that.
  • invokeExact needs the call site's types, cast included, to match the handle's type exactly.
  • keep handles in static final fields and they cost the same as a direct call.
  • combinators build handles out of handles: bind, filter, guard, catch, loop.
  • a VarHandle gives plain fields CAS and per-access memory ordering. volatile is the right default.