Reflection lets Java code inspect and manipulate types, methods, and fields at runtime. Combined with dynamic proxies, it's the foundation under most Java frameworks — Spring, Hibernate, JUnit, mocking libraries, serialization, dependency injection. Application code rarely uses reflection directly; understanding what frameworks do helps when something goes wrong.
The main APIs:
Class<?> — runtime type informationMethod, Field, Constructor — type membersMethod.invoke(target, args...) — call a method by referenceField.get(target) / Field.set(target, value) — read/write fieldsConstructor.newInstance(args...) — create objectsClass<?> clazz = Class.forName("com.example.MyClass");
Method m = clazz.getMethod("doSomething", String.class);
Object instance = clazz.getConstructor().newInstance();
m.invoke(instance, "argument");
This is opaque, slow, and fragile compared to direct method calls. Use sparingly.
For application code, reflection is almost always the wrong choice. Direct method calls, interfaces, and explicit type handling are clearer, faster, safer.
Proxy.newProxyInstance creates an object that implements specified interfaces and routes all calls through an InvocationHandler:
MyService service = (MyService) Proxy.newProxyInstance(
classLoader,
new Class[]{MyService.class},
(proxy, method, args) -> {
// do something before
Object result = realImplementation.invoke(args);
// do something after
return result;
});
Used by:
Limitations: only works on interfaces. For concrete classes, libraries use bytecode generation (CGLIB, ByteBuddy) — similar concept, different mechanism.
For some reflection use cases, modern Java has cleaner alternatives:
Faster than Method.invoke. The JVM can inline through MethodHandles in ways it cannot through reflection. Used by Java's var invocation, certain framework hot paths.
For atomic field access. Replaces Unsafe for most cases.
Where reflection was used for type dispatch (visitor pattern), modern Java often uses sealed interfaces + switch pattern matching. See JavaRecordsAndSealedClasses.
Java modules restrict reflective access. A module must opens a package for reflection from another module:
opens com.example.entity to com.example.persistence;
Without this, frameworks doing reflection on entity classes get IllegalAccessException. This is why Spring Boot's documentation recommends --add-opens flags or proper module declarations for reflection-heavy frameworks.
Reflection is slow:
For hot paths: cache lookups, pre-resolve at startup, use MethodHandle if real performance matters.
For cold paths (configuration, startup, occasional invocation): the performance cost is invisible.
setAccessible(true) for non-public members. Reflection on private fields fails without this.opens in modular code. Fails at runtime.