🔴 The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOG// CAPTURED — OpenJDK 25 Temurin: m.invoke("hi", "extra") on String.length() Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments: 1 expected: 0 at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.checkArgumentCount(DirectMethodHandleAccessor.java:324) at java.base/java.lang.reflect.Method.invoke(Method.java:580) // Pre-Java-18 wording was BARE - captured verbatim on OpenJDK 17.0.19: // java.lang.IllegalArgumentException: wrong number of arguments

⚡ Quick Fix Works 80% of the time

Match the args array length to method.getParameterCount() — and remember invoke(obj) alone means zero arguments.

Method m = String.class.getMethod("length"); Object result = m.getParameterCount() == 0 ? m.invoke("hi") // zero args: pass none : m.invoke("hi", argsArray);

🧠 Why this Happens

Tap to expand the deep technical explanation

Method.invoke flattens its Object... varargs into ONE array, boxes primitives against their wrappers, and validates the count against the resolved method BEFORE touching the target — rejecting mismatches with IllegalArgumentException before any of your code runs. The traps are structural, not exotic: passing null intending 'no args' is actually CORRECT (null is treated as empty), while invoking a varargs TARGET (void log(Object... parts)) needs explicit double-array expansion because reflection never auto-spreads. The counts arrived WITH JEP 416's method-handle rewrite in Java 18 - verified in the jdk-18-ga through jdk-25-ga sources and captured on 25; Java 17 and earlier print the bare phrase (captured on 17.0.19), leaving you counting signatures by eye.

The HITEC City Parking Spot Analogy:

A vending machine slot takes an exact coin count: insert two coins for a one-coin slot and it spits EVERYTHING back before dispensing — no partial service, no guessing.

🔁 How to Reproduce Confirm this is your error

String.class.getMethod("length").invoke("hi", "extra") — one extra argument against a zero-parameter method. (Lab capture: OpenJDK 25.)

🛠️ Solutions (5 Ways to Fix)

Solution 1✓ Most common cause

Match arity using getParameterCount()

👉 Use this when/if dispatch tables build argument arrays dynamically from configs or maps.

Construct the array sized to the resolved method's own parameterCount, filling defaults for absent optional values where your domain allows it. Asserting length == getParameterCount() before invoke turns runtime explosions into clear precondition errors.

Object[] argv = new Object[method.getParameterCount()]; for (int i = 0; i < argv.length; i++) { argv[i] = supplied(i); // or documented default } method.invoke(target, argv);
Solution 2

Master the null-means-empty idiom correctly

👉 Use this when/if code passes literal null 'just in case' or wraps single args inconsistently.

m.invoke(obj) and m.invoke(obj, (Object[]) null) BOTH mean zero arguments — null IS the empty array here. The dangerous cousin is m.invoke(obj, null): ambiguous varargs that some compilers reject. Prefer the no-extra-arg overload and say what you mean.

m.invoke(target); // zero args, idiomatic m.invoke(target, (Object[]) null); // explicit-empty variant // avoid: m.invoke(target, null) // ambiguous varargs
Solution 3

Expand varargs targets explicitly

👉 Use this when/if the TARGET method itself declares Object... or T... parameters.

Reflection performs no spread magic: for a method taking (Object...), the args array must contain EXACTLY ONE element — the actual array — i.e., new Object[]{new Object[]{a, b}}. MethodHandles.spread() offers a cleaner long-term alternative that does the expansion for you.

var logger = new Logger(); Method log = Logger.class.getMethod("log", Object[].class); // (Object...) target: args array must hold EXACTLY ONE element - the real array log.invoke(logger, new Object[]{ new Object[]{ "a", "b" } }); // cleaner: MethodHandle mh = MethodHandles.lookup() .findVirtual(Logger.class, "log", MethodType.methodType(void.class, Object[].class)); mh.asSpreader(Object[].class, 2).invokeExact(logger, "a", "b");
Solution 4

Box primitives deliberately to dodge the NEXT exception

👉 Use this when/if arity now matches but invocation still fails with argument type mismatch.

An int parameter demands a boxed Integer in the args array — int values autobox fine, but arrays built from Object literals holding Integer where long is expected fail AFTER the count check. Align each element with getParameterTypes()[i] wrappers and this exception family closes for good.

Class<?>[] pts = method.getParameterTypes(); for (int i = 0; i < pts.length; i++) { if (pts[i] == int.class) argv[i] = ((Number) raw[i]).intValue(); if (pts[i] == long.class) argv[i] = ((Number) raw[i]).longValue(); }
Solution 5

Prefer MethodHandles.Lookup for repeated invokes

👉 Use this when/if the same methods are invoked constantly and reflection overhead shows in profiles.

Lookup.findVirtual binds a strongly-typed MethodHandle ONCE — arity and types verified at creation, invokeExact enforcing both at JIT-friendly speed. Wrong counts become immediate WrongMethodTypeException at wiring time instead of per-call IllegalArgumentException.

MethodHandle len = MethodHandles.lookup() .findVirtual(String.class, "length", MethodType.methodType(int.class)); int n = (int) len.invokeExact("hi"); // arity/type locked forever

📋 Version Notes

Java 8

Plain wording: 'wrong number of arguments' — no counts.

Java 11

Wording unchanged.

Java 17

Last release with the BARE wording — captured verbatim on OpenJDK 17.0.19 (legacy NativeMethodAccessorImpl frames).

Java 21

Counted form since Java 18, where JEP 416 introduced DirectMethodHandleAccessor: 'wrong number of arguments: <got> expected: <paramCount>' — verified identical across jdk-18-ga through jdk-25-ga sources.

🛡️ How to Prevent This Next Time

Cache resolved Methods together with their parameterCount and wrapper-typed specs, build argument arrays in one validated helper, document the null-means-empty convention in team guides, and migrate hot reflective paths to cached MethodHandles.