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<?> 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

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:

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.

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.

Module system interaction

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.

Performance

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.

Common failure patterns

Further Reading