Under The Hood
backend java JDK 21+ (MethodHandles, VarHandle, invokedynamic)

MethodHandles, VarHandle, and invokedynamic Bytecode

Last updated
Prerequisites:
Java reflection basics
Understanding of the JVM bytecode and class files
  • java
  • methodhandles
  • varhandle
  • invokedynamic
  • reflection
  • bytecode

Read at your depth

The practical view

MethodHandle is a typed, directly-invocable reference to a method (or field accessor/constructor): MethodHandles.lookup().findVirtual(String.class, "length", MethodType.methodType(int.class)). MethodHandles are faster than reflection for repeated calls and can be combined (asType, bindTo, filterArguments). VarHandle (java.lang.invoke) gives typed access to fields and array elements with atomic/volatile access modes (compareAndSet, getAndAdd). invokedynamic is a bytecode instruction (used by lambdas, string concat, and language runtimes) that lets the runtime bootstrap the call target lazily and cache it. Lambda expressions compile to invokedynamic.

The same idea in other frameworks

react equivalent
N/A — JVM bytecode feature.

Legacy vs modern

Reflection invoke vs MethodHandle invoke

Reflection resolves and checks every call; a MethodHandle is a pre-bound, typed, directly-callable reference that the JIT can inline.

before → after
Reflection for repeated calls
Method m = Foo.class.getMethod("bar", int.class); // ❌ OLD
MethodHandle invoke
MethodHandle bar = lookup.findVirtual(Foo.class, "bar", MethodType.methodType(void.class, int.class)); // ✅ NEW

Interview gotchas

Context

This tests the bytecode-level dynamic linking model — interviewers want the bootstrap/link/cache mechanism and why it enables lazily-decided, cached call targets.

The mechanical answer

invokestatic/invokevirtual have a statically-known target (resolved at link time). invokedynamic defers the target decision to runtime: the call site names a bootstrap method (BSM); on first execution, the JVM calls the BSM, which returns a CallSite wrapping the target MethodHandle; the call site is then linked and cached, so subsequent executions are direct calls. This lets the runtime choose the implementation based on runtime conditions (e.g. which lambda factory, which string-concat strategy, which method in a dynamically-typed language) and lets the JIT treat the linked call as a normal direct/inlined call. For lambdas, the BSM returns a constant call site pointing to the generated lambda class's method, and the lambda instance (if stateless) can even be cached — eliminating per-creation allocation.

Trap

The naive answer is 'invokedynamic is just reflection at bytecode level'. It's a lazy, cached linking mechanism, not per-call reflection — the bootstrap runs once and the call site is bound. Another trap: assuming every invokedynamic allocates on each call — stateless lambdas use a constant call site and cached instances. Candidates who describe the BSM → CallSite → cache flow show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.