Reflection reads type and member metadata at runtime and can invoke members through reflective handles instead of a direct source-level call.
Java reflection: inspect types without breaking contracts
Java 8+. Use a JDK that supports this release.
Inspect a narrow contract
An importer needs to learn whether a plugin exposes a public identifier method. The sample requests that exact public method and invokes it on a constructed object. It does not scan private fields or infer a schema from every implementation detail.
getMethod searches public methods, including inherited ones. getDeclaredMethod examines declarations on the requested class and can find non-public members, but finding a member does not grant permission to invoke it.
On modular runtimes, access restrictions can block deep reflection. Treat an inaccessible member as a boundary to respect. Adding broad opening flags merely to preserve an accidental dependency on private fields creates a deployment requirement that callers must understand.
Separate lookup failure from target failure
A missing method is a registration error. An exception thrown by the invoked method is wrapped in InvocationTargetException; its cause is the failure from the target code. Report those cases differently so the operator can identify a contract mismatch versus a service error.
A direct interface method is usually easier to compile-check and test. Choose reflection when the program has a real runtime discovery requirement, not when a normal interface already describes the plugin.
Working program
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class PluginInspection {
public static final class ShipmentPlugin {
public String identifier() { return "shipment-plugin"; }
}
public static void main(String[] args) throws ReflectiveOperationException {
Method identifier = ShipmentPlugin.class.getMethod("identifier");
try {
Object result = identifier.invoke(new ShipmentPlugin());
System.out.println(result);
} catch (InvocationTargetException failure) {
throw new IllegalStateException("Plugin failed", failure.getCause());
}
}
}Output
shipment-pluginCost and design choices
Lookup, argument checks, and invocation have costs that differ from a direct call. Exact overhead depends on the VM, optimization, and workload. Cache resolved metadata where appropriate and measure the full registration or request path.
Caching Class or Method objects indefinitely can keep associated class loaders reachable. Plugin reload systems need cache lifetime to follow plugin lifetime.
Common Mistakes
- Do not treat getDeclaredMethod as an access grant.
- Do not discard the cause of InvocationTargetException.
- Do not rely on the unspecified order of reflected members.
Connect the contracts
Compare the boundary explained in Runtime metadata with the assumptions made by this program.
Compare the boundary explained in Retained object graphs with the assumptions made by this program.
Apply this contract in Spring
Spring AOP proxies: self-invocation bypasses proxy advice. These lessons keep framework assembly separate from the Java contract.
