Dynamic Method Invocation With Java Reflection
Using Java Reflection for Dynamic Method Invocation and Inspection allows an application to examine classes and call methods while it is running. Instead of depending entirely on compile-time types, code can discover constructors, fields, annotations and methods through the Java Reflection API.
This capability supports frameworks, plugin systems, testing tools, object mappers and dependency injection containers. It is powerful, but reflective code should be designed carefully because errors move from compile time to runtime, where they can be harder to diagnose.
Why Reflection Matters
Reflection begins with a Class<?> object. You can obtain one from an instance with getClass(), from a type with Customer.class, or by loading a class name through Class.forName(). From there, methods such as getMethods() and getDeclaredMethods() expose different parts of the class structure.
getMethods() returns public methods, including inherited methods. getDeclaredMethods() includes methods declared by the target class, even when they are private or protected. This distinction matters when building inspection utilities, as a framework may need to inspect only its own class members rather than every inherited operation.
Building A Safe Inspection Utility
A small inspector can print method names, return types and parameter types without invoking anything. The following pattern is useful for diagnostics, plugin discovery and automated documentation:
Class<?> type = Class.forName("com.example.OrderService");
for (Method method : type.getDeclaredMethods()) {
String parameters = Arrays.stream(method.getParameterTypes())
.map(Class::getSimpleName)
.collect(Collectors.joining(", "));
System.out.printf("%s(%s) -> %s%n",
method.getName(),
parameters,
method.getReturnType().getSimpleName());
}
Inspection becomes more reliable when it filters methods deliberately. An application might accept only public methods with a custom annotation, rather than exposing every method discovered through reflection. This approach is particularly important in a Spring application, where proxies and generated classes can make the runtime type differ from the class developers expect.
Useful Inspection Checks
- Confirm that a method has the expected annotation before using it.
- Verify parameter count and compatible parameter types.
- Reject synthetic or bridge methods when they are irrelevant.
- Record declaring class and visibility for useful diagnostics.
Invoking Methods At Runtime
Dynamic invocation uses Method.invoke(). First, locate a method by name and compatible parameter types, then supply the target object followed by the argument values. For a static method, the target object is null.
Method method = type.getDeclaredMethod("calculateTotal", BigDecimal.class);
method.setAccessible(true);
Object result = method.invoke(orderService, new BigDecimal("149.95"));
BigDecimal total = (BigDecimal) result;
The call may throw NoSuchMethodException, IllegalAccessException or InvocationTargetException. The last exception wraps an error thrown by the invoked method, so production code should inspect getCause() before logging or translating the failure.
Modern Java also has stronger access rules around modules and encapsulation. setAccessible(true) may fail when a package is not open to the calling module. Public APIs, annotations and explicit extension points are generally safer than forcing access to private implementation details.
Handling Arguments And Results
A robust invocation layer should separate method lookup, argument conversion and exception handling. For example, a JSON-based endpoint may receive a string "42" while the target method expects an Integer. Reflection will not automatically perform every business-level conversion, so the adapter needs a clear conversion policy.
The return value is always represented as an Object, with primitive results boxed automatically. Null values, overloaded methods and varargs require careful treatment. A method accepting long is not necessarily matched in the same way as one accepting Long, and choosing an overload only by method name can produce ambiguous or unsafe behaviour.
Runtime Invocation Safeguards
- Allow calls only to an explicit allowlist of methods.
- Validate nullability, numeric ranges and argument types.
- Unwrap
InvocationTargetExceptionbefore recording failures. - Preserve the original cause and useful correlation details.
- Avoid exposing sensitive objects or internal exception messages.
Managing Security And Performance
Reflection can become a security risk when class names, method names or arguments come from untrusted users. An administrative API that invokes arbitrary methods could expose database operations, filesystem access or credential-handling code. Authentication and authorisation must happen before method selection, not after invocation begins.
There is also a performance cost from repeated method discovery and access checks. Cache immutable metadata such as Method objects and parameter descriptors when the application has stable classes. Measure before optimising, though: network calls, database queries and serialisation commonly cost far more than a reflective call.
Applying Reflection In Australian Systems
Reflection appears in practical systems across Australian businesses, from Melbourne retail platforms to Brisbane logistics services and Sydney-based financial applications. A plugin architecture can load payment, shipping or reporting modules without hard-coding every integration, while annotation-driven dispatch can connect domain operations to REST endpoints.
Australian deployments also need operational discipline around Australian Eastern or Central time zones, daylight saving differences and privacy obligations. A reflective mapper handling customer records should avoid exposing Medicare-related data, identity documents or payment details through a generic inspection endpoint. Clear logs and restricted method registries support safer production operations.
Teams working with local retailers, councils or growing SaaS companies may benefit from Java consultancy advice when deciding whether reflection belongs in a framework boundary or a core business service. In most cases, reflection is best kept at integration edges, with ordinary typed Java code underneath for maintainability and compiler support.