Java Reflection is a powerful mechanism that enables programs to inspect and manipulate classes, methods, fields, and annotations at runtime—without compile-time knowledge of their structure. While Java is statically typed, reflection bridges the gap toward dynamic behavior, supporting frameworks like Spring, Hibernate, and JUnit.
Core Capabilities of Reflection
Reflection allows you to:
- Determine the class of any object at runtime
- Instantiate objects dynamically—even for classes unknown at compile time
- Discover and inspect constructors, methods, and fields—including private and synthetic members
- Read generic type information (e.g.,
Map<String, Integer>) viaTypehierarchy - Invoke methods and access/modify fields regardless of visibility (with security permission)
- Process annotations programmatically
- Construct proxy instances for interface-based delegation (e.g., AOP interceptors)
The Class Object: The Reflection Entry Point
Every object in Java inherits getClass(), returning a Class<?> instance—the canonical representation of its runtime type. This singleton instance encapsulates metadata about the loaded class, including inheritance hierarchy, decalred members, annotations, and classloader context.
Key properties of Class:
- Immutable and JVM-unique per loaded type
- Represents not only classes but also interfaces, enums, annotations, arrays, primitives, and
void - Serves as the root for all reflective operations
Obtaining a Class Instance — Four Idiomatic Ways
- Class literal:
Class<User> cls = User.class;— fastest, compile-time safe - Instance method:
Class<?> cls = userObj.getClass();— requires an existing instance - Static lookup:
Class<?> cls = Class.forName("com.example.User");— throwsClassNotFoundException - ClassLoader API:
Class<?> cls = this.getClass().getClassLoader().loadClass("com.example.User");— lower-level, bypasses initialization
Essential Class Methods
| Method | Purpose |
|---|---|
getSuperclass() |
Returns the direct superclass as Class; returns null for Object or primitives |
getInterfaces() |
Returns all directly implemented interfaces |
getDeclaredFields() |
Returns all fields (including private) declared in this class—not inherited |
getDeclaredMethods() |
Returns all methods (including private/static) declared here |
getDeclaredConstructors() |
Returns all constructors, regardless of visibility |
getGenericSuperclass() |
Returns Type with full generic signature (e.g., ArrayList<String>) |
getAnnotations() |
Returns all annotations directly present on the class |
Dynamic Instantiation and Member Access
Unlike static instantiation, reflection supports creation and interaction without compile-time binding:
public class Vehicle {
private final String model;
private int speed;
public Vehicle(String model) { this.model = model; }
public void accelerate(int delta) { this.speed += delta; }
private void logStart() { System.out.println("Engine started for " + model); }
}
// Reflective usage
try {
Class<?> vehicleClass = Class.forName("Vehicle");
// Instantiate using declared constructor
Constructor<?> ctor = vehicleClass.getDeclaredConstructor(String.class);
ctor.setAccessible(true); // Bypass private access check
Object car = ctor.newInstance("Tesla Model S");
// Invoke private method
Method logMethod = vehicleClass.getDeclaredMethod("logStart");
logMethod.setAccessible(true);
logMethod.invoke(car);
// Modify private field
Field speedField = vehicleClass.getDeclaredField("speed");
speedField.setAccessible(true);
speedField.set(car, 80);
} catch (Exception e) {
throw new RuntimeException(e);
}
Safe Reflection with setAccessible()
The setAccessible(true) call disables Java’s access control checks for a specific Field, Method, or Constructor. Use judiciously:
- Required to access non-public members
- May trigger
SecurityExceptionunder strict policies - Can impact performance if overused (JVM may skip optimizations)
- Should be paired with try-with-resources or explicit cleanup in production code
Runtime Structure Discovery Example
This snippet introspects a generic entity and prints its structural blueprint:
public class Introspector {
public static void describe(Class<?> clazz) {
System.out.printf("=== %s ===%n", clazz.getSimpleName());
// Superclass & interfaces
System.out.println("Extends: " + Optional.ofNullable(clazz.getSuperclass())
.map(Class::getSimpleName).orElse("Object"));
Arrays.stream(clazz.getInterfaces())
.forEach(iface -> System.out.println("Implements: " + iface.getSimpleName()));
// Constructors
Arrays.stream(clazz.getDeclaredConstructors())
.forEach(c -> System.out.println("Ctor: " + c));
// Public methods only
Arrays.stream(clazz.getMethods())
.filter(m -> !m.isDefault() && !m.getName().startsWith("wait"))
.forEach(m -> System.out.println("Method: " + m.getName() +
"(" + Arrays.toString(m.getParameterTypes()) + ") → " + m.getReturnType().getSimpleName()));
}
public static void main(String[] args) {
describe(ArrayList.class);
}
}
Dynamic Proxies: Interception Without Inheritance
Java’s java.lang.reflect.Proxy generates runtime implementations of interfaces, delegating calls through an InvocationHandler. This avoids subclassing and enables cross-cutting concerns (logging, auth, metrics):
interface PaymentProcessor {
boolean charge(double amount);
}
class RealPaymentProcessor implements PaymentProcessor {
public boolean charge(double amount) {
System.out.printf("Processing $%.2f payment%n", amount);
return true;
}
}
class AuditHandler implements InvocationHandler {
private final Object target;
AuditHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[AUDIT] Calling " + method.getName());
Object result = method.invoke(target, args);
System.out.println("[AUDIT] Completed " + method.getName());
return result;
}
}
// Usage
PaymentProcessor proxy = (PaymentProcessor) Proxy.newProxyInstance(
PaymentProcessor.class.getClassLoader(),
new Class[]{PaymentProcessor.class},
new AuditHandler(new RealPaymentProcessor())
);
proxy.charge(99.99); // Triggers audit logs before/after
ClassLoader Fundamentals
Classes are loaded in phases:
- Loading: Bytecode read (from JAR, filesystem, network), converted to
Classinstance - Linking: Verification (bytecode safety), preparation (static field memory), resolution (symbolic → direct references)
- Initialization: Execution of
<clinit>(static blocks + field initializers), triggered by first active use
Three built-in loaders form a delegation hierarchy:
- Bootstrap Loader: Native, loads
rt.jar(e.g.,java.lang.*). Not accessible via Java API. - Extension Loader: Loads
$JAVA_HOME/jre/lib/extor paths fromjava.ext.dirs. - Application (System) Loader: Loads
-cporCLASSPATH. Default for app classes.
Custom loaders enable modularization, hot-reloading, sandboxing, and plugin architectures.