Q: How does Java reflection work? When to use it, what are the costs?
Answer:
Reflection = inspect + manipulate classes/methods/fields at runtime. The class metadata in the JVM is exposed via java.lang.reflect.
Basic Operations
Class<?> c = Class.forName("com.acme.User");
// or User.class, or user.getClass()
// Inspect
c.getDeclaredFields();
c.getDeclaredMethods();
c.getDeclaredConstructors();
c.getInterfaces();
c.getSuperclass();
c.isAnnotationPresent(Entity.class);
// Instantiate
Constructor<?> ctor = c.getDeclaredConstructor(String.class, int.class);
Object instance = ctor.newInstance("alice", 30);
// Invoke method
Method m = c.getDeclaredMethod("greet", String.class);
m.setAccessible(true); // bypass private
Object result = m.invoke(instance, "world");
// Read/write field
Field f = c.getDeclaredField("name");
f.setAccessible(true);
f.set(instance, "bob");
String name = (String) f.get(instance);
Annotation Reading
for (Method m : c.getDeclaredMethods()) {
if (m.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation ann = m.getAnnotation(MyAnnotation.class);
System.out.println(ann.value());
}
}
Where Reflection Powers The Java Ecosystem
- Spring — bean instantiation, DI,
@Autowiredfield injection, AOP proxies. - Hibernate / JPA — entity field access, lazy proxies.
- Jackson / Gson — serialize/deserialize without manual mappings.
- JUnit / TestNG — discover
@Testmethods. - Mockito — mock generation.
- Logging frameworks, ORM, IoC, validators (Bean Validation), serializers, deserializers, ...
Costs
1. Performance
Reflection is slower than direct calls. JIT can optimize repeated reflective calls (caching MethodAccessor), but not as well as direct invocation.
Rough rule of thumb (varies, measure for your case):
- Direct call: ~1ns
- Cached
Method.invoke: ~10-50ns - Uncached: 100s of ns to µs
Avoid in tight loops. Cache Method/Field references.
2. No compile-time safety Method names are strings → typos blow up at runtime, not compile time.
3. Strong encapsulation (Java 9+)
JPMS modules + --illegal-access controls block deep reflection on JDK internals. Setting setAccessible(true) on private members of other modules requires the module to opens the package.
Add-Opens=java.base/java.lang=ALL-UNNAMED # JAR manifest, e.g., for older libs
4. Security
Bypassing private violates encapsulation contracts. Avoid in production code.
Modern Alternatives
1. MethodHandle (Java 7+)
Faster than reflection. JIT-friendly.
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findVirtual(User.class, "greet", MethodType.methodType(String.class, String.class));
String s = (String) mh.invokeExact(user, "world");
2. VarHandle (Java 9+)
For fields. Replaces sun.misc.Unsafe for atomic operations.
3. Annotation processors / code generation Lombok, MapStruct, Dagger: generate code at compile time → no runtime reflection cost.
4. Records + pattern matching Reduces need for reflective deconstruction.
Real Reflection Examples
Generic factory
public static <T> T newInstance(Class<T> c) {
try { return c.getDeclaredConstructor().newInstance(); }
catch (Exception e) { throw new RuntimeException(e); }
}
Find all fields with annotation
List<Field> idFields = Arrays.stream(c.getDeclaredFields())
.filter(f -> f.isAnnotationPresent(Id.class))
.toList();
Dynamic proxy (no aspect framework needed)
@SuppressWarnings("unchecked")
public static <T> T loggingProxy(T target, Class<T> iface) {
return (T) Proxy.newProxyInstance(
iface.getClassLoader(),
new Class<?>[]{ iface },
(proxy, method, args) -> {
System.out.println("calling " + method.getName());
return method.invoke(target, args);
});
}
Generic Type Erasure + Reflection
Generic types erased at runtime, but declared types preserved on fields, methods, classes:
Field f = User.class.getDeclaredField("orders"); // List<Order> orders;
ParameterizedType pt = (ParameterizedType) f.getGenericType();
Class<?> actualType = (Class<?>) pt.getActualTypeArguments()[0]; // Order.class
Best Practices
- Cache
Method,Field,Constructorlookups. - Catch and wrap checked exceptions sensibly.
- Prefer
MethodHandleoverMethod.invokein hot paths. - Prefer compile-time generation (annotation processors) over runtime reflection.
- Don't use reflection to break encapsulation in your own code — it's for frameworks.