W3docs

Java Reflection: Invoking Methods

Inspect and invoke methods reflectively in Java with the Method class.

Java Reflection: Invoking Methods

A Method object describes one method and — crucially — lets you call it: method.invoke(target, args...). This is the reflective heart of test runners (find @Test methods, invoke them), of frameworks dispatching to handlers by name, and of scripting bridges. This chapter covers finding methods, the parameter-type matching that trips everyone up, invoking instance and static methods, return values, and how exceptions are wrapped.

Finding methods

You look a method up by name plus parameter types — the parameter types are how Java distinguishes overloads:

Class<?> c = Calc.class;

Method m1 = c.getMethod("add", int.class, int.class);          // public, incl. inherited
Method m2 = c.getDeclaredMethod("secret", String.class);       // any access, this class only

Method[] pub = c.getMethods();           // all public methods, incl. Object's and inherited
Method[] own = c.getDeclaredMethods();   // all access levels, declared here only

The parameter Class objects must match the declared parameter types exactly — there is no overload resolution and no widening. getMethod("add", Integer.class, Integer.class) will not find add(int, int); you must pass int.class. A wrong combination throws NoSuchMethodException. For a no-arg method, pass no class arguments: getMethod("toString").

Invoking: instance, static, and arguments

invoke takes the target object first, then the arguments as a varargs Object[]:

Calc calc = new Calc();
Method add = Calc.class.getMethod("add", int.class, int.class);
Object result = add.invoke(calc, 2, 3);     // → Integer 5 (autoboxed)
int sum = (int) result;                       // unbox manually

For a static method, the target is ignored — pass null:

Method parse = Integer.class.getMethod("parseInt", String.class);
Object n = parse.invoke(null, "42");          // → Integer 42

Primitive arguments are autoboxed into the Object[]; the runtime unboxes them to match the primitive parameters. The return value is always Object — primitives come back boxed, void methods return null.

How exceptions surface: InvocationTargetException

This is the single most important gotcha. If the invoked method throws, invoke does not propagate that exception directly. It wraps it in an InvocationTargetException, and you retrieve the real one with getCause():

try {
  riskyMethod.invoke(target);
} catch (InvocationTargetException e) {
  Throwable real = e.getCause();    // the exception the method actually threw
  // handle 'real', not 'e'
}

The other checked exceptions are about the reflection call itself, not the method's body:

  • IllegalAccessException — the method is inaccessible and you didn't setAccessible(true).
  • IllegalArgumentException — wrong number/types of arguments, or wrong target type.
  • NoSuchMethodException — thrown at lookup time, not invoke time.

So: lookup failures and argument mistakes throw "directly", but anything the method's own code throws is bottled inside InvocationTargetException.

Return types, varargs, and generics

  • Return type metadata: m.getReturnType() (erased Class) and m.getGenericReturnType() (Type, keeps generics).
  • Parameters: m.getParameterTypes(), m.getParameterCount(), and m.getParameters() (names available if compiled with -parameters).
  • Varargs: a String... parameter is really String[]. Look it up with getMethod("f", String[].class) and invoke by passing an actual array, or rely on the fact that invoke will accept a trailing array for the varargs slot.
  • Bridge/synthetic methods: generic classes generate hidden bridge methods; filter them with m.isBridge() / m.isSynthetic() when enumerating.

A worked example: a mini command dispatcher

The program builds a tiny dispatcher that maps string commands to methods on a service object, invokes them reflectively with parsed arguments, handles a method that throws (to show the InvocationTargetException unwrap), and calls a static factory with a null target.

java— editable, runs on the server

What to take from the run:

  • The static factory was called with factory.invoke(null) — for a static method the target object is irrelevant, so null is the convention. The dispatcher then reused the same invoke mechanism for instance methods, passing the real calc as the target. One API, both kinds of method.
  • divide(1, 0) did not throw ArithmeticException out of invoke. It threw InvocationTargetException, and the genuine ArithmeticException: / by zero was found via getCause(). Every framework that calls user code reflectively has to unwrap this; forgetting to is why you sometimes see a confusing InvocationTargetException in a stack trace instead of the real error.
  • Looking up add with Integer.class parameters failed with NoSuchMethodException even though add(int,int) exists. Reflection matches parameter types exactly with no boxing or widening — int.class and Integer.class are different keys. This is the most common reflection bug and the reason primitive .class literals matter.
  • The private secret method was invokable only after getDeclaredMethod + setAccessible(true). As with fields, the lookup flavour (getDeclared…) and the access gate (setAccessible) are two independent steps; you need both to reach a private member.
  • Return values arrived as Object and were cast at the call site ((Calculator) factory.invoke(...)), while add's int came back autoboxed as Integer. Reflection has no static knowledge of return types, so the caller is responsible for the cast/unbox — and a wrong cast surfaces as a ClassCastException at runtime, not compile time.

Practice

Practice

You invoke a method reflectively with 'm.invoke(obj)', and the method's body throws an 'IllegalStateException'. In your calling code, which exception do you actually catch, and how do you reach the 'IllegalStateException'?