Java Reflection and Proxies
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.
What reflection provides
The main APIs:
- `Class<?>` — runtime type information
- `Method`, `Field`, `Constructor` — type members
- `Method.invoke(target, args...)` — call a method by reference
- `Field.get(target)` / `Field.set(target, value)` — read/write fields
- `Constructor.newInstance(args...)` — create objects
```java
Class<?> 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.
When reflection earns its place
- **Frameworks** that need to operate on user-defined types
- **Serialization** that must work without prior knowledge of types
- **Test frameworks** discovering and running annotated tests
- **Dependency injection** wiring up beans by type and qualifier
- **Plugin systems** loading classes at runtime
For application code, reflection is almost always the wrong choice. Direct method calls, interfaces, and explicit type handling are clearer, faster, safer.
Dynamic proxies
`Proxy.newProxyInstance` creates an object that implements specified interfaces and routes all calls through an `InvocationHandler`:
```java
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:
- AOP frameworks (Spring AOP, AspectJ)
- Mocking libraries (Mockito creates proxy objects)
- RPC clients (the proxy translates method calls into network requests)
- Lazy loading (Hibernate proxies)
Limitations: only works on interfaces. For concrete classes, libraries use bytecode generation (CGLIB, ByteBuddy) — similar concept, different mechanism.
Modern alternatives
For some reflection use cases, modern Java has cleaner alternatives:
MethodHandles
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.
VarHandles
For atomic field access. Replaces `Unsafe` for most cases.
Sealed types + pattern matching
Where reflection was used for type dispatch (visitor pattern), modern Java often uses sealed interfaces + switch pattern matching. See [JavaRecordsAndSealedClasses](JavaRecordsAndSealedClasses).
Module system interaction
Java modules restrict reflective access. A module must `opens` a package for reflection from another module:
```java
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.
Performance
Reflection is slow:
- Method lookup is expensive (cache the Method object)
- Method invocation has 5–10× overhead vs. direct calls in tight loops
- Field access is similarly slower
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.
Common failure patterns
- **Using reflection in application code where direct calls would work.** Slower, less safe, harder to read.
- **Not setting `setAccessible(true)` for non-public members.** Reflection on private fields fails without this.
- **Catching reflection exceptions and ignoring them.** The exception usually points to a real problem.
- **Reflective access without `opens` in modular code.** Fails at runtime.
- **Overusing dynamic proxies.** Each layer of proxying adds overhead and obscurity.
Further Reading
- [JavaModuleSystem](JavaModuleSystem) — Module access and reflection
- [JavaAnnotationProcessing](JavaAnnotationProcessing) — Compile-time alternative to reflection
- [JavaTwentyOneFeatures](JavaTwentyOneFeatures) — Modern features that reduce reflection need
- [SpringBootFundamentals](SpringBootFundamentals) — A heavy reflection user
- [Java Hub](JavaHub) — Cluster index