skip to content

What is the difference between MethodHandle.invokeExact() and invoke(), and why does invokeExact often throw WrongMethodTypeException?

level: seniorimportance: should knowfreq 40%

answer

  1. Both are signature-polymorphic: call-site types = demanded type
  2. invokeExact = identical match, no conversions
  3. invoke = asType conversions (widen/box/cast)
  4. Return type is part of the contract -> cast the result
  5. WrongMethodTypeException = signature mismatch at runtime

basics

~20 s

invokeExact requires the call's argument and return types to match the handle's signature exactly, with no conversions. invoke is more forgiving: it will box, unbox, widen, or cast as needed. invokeExact throws WrongMethodTypeException when the static types at the call site do not match precisely.

solid answer

~50 s

Both invoke and invokeExact are signature-polymorphic methods on MethodHandle, meaning the compiler treats the types you pass at the call site as the demanded signature. invokeExact demands an *exact* match between the handle's MethodType and the types as written at the call site - including the return type, which is inferred from the assignment context or an explicit cast. Any mismatch (an int where long is expected, an Object where String is expected, or an unboxed where boxed is expected) throws WrongMethodTypeException at runtime. invoke is permissive: it conceptually calls asType to apply the allowed conversions - widening primitives, boxing/unboxing, reference casts - before dispatching. The common gotcha is that invokeExact's return type is part of the contract: calling it in a statement context, or not casting the return value to the handle's exact return type, mismatches the signature and throws. invokeExact is faster (no conversion path) and should be preferred when you control the types.

go deeper

for a junior

Can say invokeExact needs an exact match and invoke allows conversions; may not know why the return type matters.

for a middle

Explains that invoke boxes/widens/casts while invokeExact does not, and recognises WrongMethodTypeException as a signature mismatch.

for a senior

Articulates signature polymorphism - call-site types form the demanded type - and the return-type-cast trap, plus the asType conversion model behind invoke.

for a principal

Reasons about performance trade-offs, JIT inlining of exact calls, and uses asType to pre-adapt handles for hot paths; can debug WrongMethodTypeException from type() vs descriptor.

### Signature polymorphism - the surprising bit `invoke` and `invokeExact` are not ordinary methods. They are declared as **signature-polymorphic**: their source signature is `(Object...)Object`, but the Java compiler does *not* compile a call to them as a varargs `Object[]` call. Instead, it emits an `invokevirtual` bytecode whose **descriptor is exactly the types you wrote at the call site**. So `mh.invokeExact(3, 4)` where both are `int` and the result is assigned to an `int` compiles to a call demanding the signature `(int,int)int`. This means: *the types as written at the call site become the demanded MethodType.* Understanding this is the key to everything else. ### invokeExact - no conversions allowed `invokeExact` requires that the **demanded** signature (from the call site) be **identical** to the handle's actual `type()`. Identical means: - same parameter types, in order, with no widening, boxing, or casting; - **the same return type**, which is determined by the surrounding context (an assignment target or an explicit cast). If they differ in any way, you get a `WrongMethodTypeException` **at runtime** (the bytecode descriptor and the handle's type are compared when the call executes). ```java MethodHandle mh = MethodHandles.lookup() .findStatic(Math.class, "max", MethodType.methodType(int.class, int.class, int.class)); int r = (int) mh.invokeExact(3, 4); // OK: demanded (int,int)int == handle type // long bad = (long) mh.invokeExact(3,4); // throws WrongMethodTypeException: returns int, not long // mh.invokeExact(3, 4); // in a void/statement context the demanded return is void -> throws // Object o = mh.invokeExact(3, 4); // demanded return Object != int -> throws ``` #### The number-one trap: the return type Because the return type is part of the demanded signature, **you almost always must cast the result of invokeExact to the handle's exact return type** - e.g. `(int) mh.invokeExact(...)`. Forgetting the cast (or using it in a statement where the demanded return type becomes `void`) is the classic cause of `WrongMethodTypeException`. Likewise the cast must be the *exact* type: `(long)` on an `int`-returning handle fails - no widening. ### invoke - asType conversions are applied `invoke` is forgiving. Conceptually it behaves as if it first calls `asType` on the handle to adapt it from its actual type to the demanded call-site type, applying the **allowed conversions**: - primitive **widening** (`int`->`long`, `int`->`double`, etc.); - **boxing and unboxing** (`int`<->`Integer`); - reference **up/down casts** (with a runtime `ClassCastException` if a downcast fails); - `void` adaptation (a returned value can be dropped). So `Object r = mh.invoke(3, 4);` succeeds: the `int` result is boxed to `Integer` and returned as `Object`. This flexibility costs a little: the conversion path is extra work, so `invoke` is somewhat slower than a matched `invokeExact`. ### invokeWithArguments There is also `invokeWithArguments(Object... args)`, fully dynamic: arguments come as a runtime array and all conversions (including arity adjustment via spreading) are applied. It is the slowest, closest in spirit to reflective `Method.invoke`. ### Performance and guidance - Prefer **invokeExact** when you statically know the types - it is the fastest because there is no conversion adapter and the JIT can inline cleanly. - Use **invoke** when you genuinely need conversions or want fewer explicit casts and can accept the small overhead. - If you keep hitting `WrongMethodTypeException`, print `mh.type()` and compare it character-for-character with what your call site demands (especially the return type). ### Mental model Think of `invokeExact` as a function with a fixed, rigid plug shape: the socket (call site) must match exactly. `invoke` is the same plug with a built-in adapter that reshapes compatible plugs. The adapter is convenient but not free, and it can only handle *compatible* shapes - incompatible ones still fail.

  • Why must you usually cast the result of invokeExact?
    Because the return type is part of the demanded signature; the cast tells the compiler the exact return type so the emitted descriptor matches the handle's type(). Without it the demanded return is wrong (often Object or void) and you get WrongMethodTypeException.
  • How could you turn an invoke-style mismatch into a reusable exact handle?
    Call mh.asType(desiredType) once to build an adapted handle whose type() matches the call site, then use invokeExact on it - the conversions are baked into the adapter and amortised.

saying these in an interview costs you the question

  • Thinking invokeExact compares only argument types and ignores the return type
  • Believing WrongMethodTypeException is a compile-time error (it is runtime)
  • Assuming invoke is just a slower alias for invokeExact rather than applying conversions
  • Forgetting that a statement-context call makes the demanded return type void

context