How do MethodHandles compare to the classic Reflection API in terms of access checks and performance?
answer
- Reflection: check per-call + boxed Object[] + JIT-opaque
- Handle: check once at lookup, no boxing with invokeExact
- static final handle -> JIT inlines like a constant
- privateLookupIn replaces setAccessible
- Reflection simpler for introspection; handles for hot dispatch
basics
~20 sReflection 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 sWith 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
Can say handles are generally faster than reflection and that reflection boxes arguments.
Identifies the per-call access check and boxing as reflection's costs and that handles check access once at lookup.
Explains JIT inlining of constant handles, invokeExact avoiding boxing, and privateLookupIn as the setAccessible replacement; gives balanced when-to-use guidance.
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