skip to content

How do MethodHandles compare to the classic Reflection API in terms of access checks and performance?

level: seniorimportance: should knowfreq 42%

answer

  1. Reflection: check per-call + boxed Object[] + JIT-opaque
  2. Handle: check once at lookup, no boxing with invokeExact
  3. static final handle -> JIT inlines like a constant
  4. privateLookupIn replaces setAccessible
  5. Reflection simpler for introspection; handles for hot dispatch

basics

~20 s

Reflection checks access on each use (unless you call setAccessible), boxes arguments into an Object array, and is hard for the JIT to optimise. A MethodHandle checks access once at lookup time and, especially with invokeExact, is much closer to a direct call, so it usually performs better.

solid answer

~50 s

With reflection (java.lang.reflect.Method), access is governed by the Method object and checked at invoke time unless suppressed with setAccessible; calls pass arguments as a boxed Object[] and return a boxed Object, and the indirection is opaque to the JIT, so dynamic invocation is comparatively slow. MethodHandles move the access check to lookup time: a Lookup performs the visibility check when find* resolves the handle, so subsequent invocations carry no access cost. A MethodHandle is strongly typed via its MethodType, and invokeExact avoids boxing entirely. Critically, when a MethodHandle is held in a static final field, the JIT can treat it almost like a constant and inline through it, approaching the speed of a direct invokevirtual. The trade-offs: handles are immutable and composable (bind, filter, fold), but the API is lower-level and the signature-exactness of invokeExact is easy to get wrong. For repeated hot-path dynamic dispatch, handles win; for one-off introspection, reflection is simpler.

go deeper

for a junior

Can say handles are generally faster than reflection and that reflection boxes arguments.

for a middle

Identifies the per-call access check and boxing as reflection's costs and that handles check access once at lookup.

for a senior

Explains JIT inlining of constant handles, invokeExact avoiding boxing, and privateLookupIn as the setAccessible replacement; gives balanced when-to-use guidance.

for a principal

Reasons about constant-folding/@Stable semantics, when handle gains evaporate (non-constant handles), and architects dynamic-dispatch layers (invokedynamic call sites) accordingly.

### Two ways to call a method you only know at runtime Sometimes a program must invoke a method whose identity is not known until runtime - a plugin, a serialiser, an ORM. Java offers two mechanisms: 1. **Reflection** (`java.lang.reflect`, since Java 1.1): `Class.getMethod(...)` returns a `Method`, and `method.invoke(target, args)` calls it. 2. **Method handles** (`java.lang.invoke`, since Java 7): a `Lookup` resolves a `MethodHandle`, which you call with `invokeExact`/`invoke`. ### Access checks: when and where - **Reflection:** the `Method` object enforces accessibility. By default, invoking a non-public member from outside its visibility throws `IllegalAccessException`; you can suppress the check with `setAccessible(true)` (subject to the module system / `--add-opens`). The check is associated with the `Method` and can be re-evaluated per use. - **Method handles:** the **Lookup** captures the access context of the class that created it, and the visibility check happens **once, at lookup time** (`findVirtual`, `findStatic`, ...). After that, the returned handle is an unrestricted callable - invocation does **no** access check. To reach private members of another class you obtain a `privateLookupIn(target, callerLookup)` handle (the modern, capability-based replacement for `setAccessible`). The practical upshot: with handles, the expensive permission logic is paid up front and amortised; with reflection it can recur on each call unless you cache an accessible `Method`. ### Performance Three factors make reflective invocation slow: 1. **Boxing:** `Method.invoke(Object, Object...)` boxes every primitive argument and the return value, allocating wrappers and an array. 2. **Per-call checks:** access (and sometimes argument-type) checks on each call. 3. **Opacity to the JIT:** the JIT cannot easily see through the reflective indirection to inline the target. Method handles address all three: 1. **No boxing with invokeExact:** the call descriptor carries the real primitive types. 2. **No per-call access check:** done at lookup. 3. **JIT-friendliness:** a MethodHandle stored in a `static final` field is treated by HotSpot as a **constant** (a `@Stable`/constant-foldable value). The JIT can then inline straight through to the target, so a constant handle invoked with `invokeExact` can approach the cost of a hand-written direct call. > Caveat: a MethodHandle that is *not* a compile-time-ish constant (e.g. read from a non-final field or a map each call) loses much of the inlining advantage and may be only modestly faster than cached reflection. The big wins come from constant handles in hot paths. ### Other differences - **Immutability & composition:** handles are immutable; you build new handles by transforming them (`insertArguments`/`bindTo`, `filterArguments`, `foldArguments`, `dropArguments`, `asType`). Reflection has no such combinator algebra. - **Type model:** handles are typed (`MethodType`); reflection is `Object`-centric. - **Ergonomics:** reflection is simpler for ad-hoc introspection (listing fields, reading annotations); handles are a lower-level, sharper tool. - **Foundation role:** `invokedynamic`, lambdas, method references, and string concatenation are built on method handles, not reflection. ### When to choose which - **Reflection:** one-off or infrequent calls, introspection, reading annotations/metadata, quick scripts. Cache the `Method` and `setAccessible` if it is on a warm path. - **Method handles:** repeated dynamic dispatch on a hot path, building dynamic call sites / DSLs, frameworks that want near-direct-call speed, anything that integrates with `invokedynamic`. ### Mental model Reflection is a universal remote: works on everything, re-negotiates the connection each press. A method handle is a pre-paired remote: you pay once to pair (lookup), and afterwards each press is nearly instant - but you must pair it correctly and hold it as a constant to get the full benefit.

  • Why does holding a MethodHandle in a static final field matter for speed?
    HotSpot treats a static final MethodHandle as a constant, allowing it to constant-fold and inline through to the target, approaching direct-call performance. A handle read from a mutable field each call loses that optimisation.
  • What replaces setAccessible(true) when you need private access via handles?
    MethodHandles.privateLookupIn(targetClass, callerLookup) - it returns a Lookup with private access to the target, subject to the module system, giving capability-based deep access instead of mutating an AccessibleObject flag.

saying these in an interview costs you the question

  • Claiming any MethodHandle is always faster than reflection regardless of how it is stored
  • Saying handles do no access check at all (they check once, at lookup)
  • Forgetting that reflection can cache an accessible Method to cut some overhead
  • Believing reflection can be inlined by the JIT as well as a constant handle

context