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 onlyThe 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 manuallyFor a static method, the target is ignored — pass null:
Method parse = Integer.class.getMethod("parseInt", String.class);
Object n = parse.invoke(null, "42"); // → Integer 42Primitive 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'tsetAccessible(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()(erasedClass) andm.getGenericReturnType()(Type, keeps generics). - Parameters:
m.getParameterTypes(),m.getParameterCount(), andm.getParameters()(names available if compiled with-parameters). - Varargs: a
String...parameter is reallyString[]. Look it up withgetMethod("f", String[].class)and invoke by passing an actual array, or rely on the fact thatinvokewill 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.
What to take from the run:
- The static factory was called with
factory.invoke(null)— for astaticmethod the target object is irrelevant, sonullis the convention. The dispatcher then reused the sameinvokemechanism for instance methods, passing the realcalcas the target. One API, both kinds of method. divide(1, 0)did not throwArithmeticExceptionout ofinvoke. It threwInvocationTargetException, and the genuineArithmeticException: / by zerowas found viagetCause(). Every framework that calls user code reflectively has to unwrap this; forgetting to is why you sometimes see a confusingInvocationTargetExceptionin a stack trace instead of the real error.- Looking up
addwithInteger.classparameters failed withNoSuchMethodExceptioneven thoughadd(int,int)exists. Reflection matches parameter types exactly with no boxing or widening —int.classandInteger.classare different keys. This is the most common reflection bug and the reason primitive.classliterals matter. - The
private secretmethod was invokable only aftergetDeclaredMethod+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
Objectand were cast at the call site ((Calculator) factory.invoke(...)), whileadd'sintcame back autoboxed asInteger. Reflection has no static knowledge of return types, so the caller is responsible for the cast/unbox — and a wrong cast surfaces as aClassCastExceptionat runtime, not compile time.
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'?