Definition and Purpose
Reflection is a mechanism that allows a program to examine and modify its own structure and behavior at runtime: classes, fields, methods, constructors, annotations, access modifiers, etc.
Basic packages and classes: java.lang.Class, java.lang.reflect.Field/Method/Constructor, java.lang.reflect.Modifier, java.lang.reflect.Proxy, java.lang.invoke.MethodHandles/VarHandle.
When is it needed:
- Frameworks / containers (Spring, JPA/Hibernate, Jackson) — injection, serialization, proxies;
- Universal libraries (mappers, validators, DI/AOP);
- Testing/mocks (JUnit, Mockito);
- Metadata inspection (annotations), plugin systems.
How it works under the hood (brief overview)
- Class loaded by ClassLoader → its metadata is available through the
Class<?>object. - We get Fields/Methods/Constructors, annotations, modifiers through
Class<?>. - Using
AccessibleObject#setAccessible(true)(Java 8) orcanAccess/modular “opens” (Java 9+) we can access non-public members (with module system restrictions). - We call
Method.invoke, read/writeField.get/set, create an object throughConstructor.newInstance. - For dynamic behavior replacement — dynamic proxies (
Proxy.newProxyInstance) or CGLIB (in Spring), for fast invocation —MethodHandles/VarHandle.
Where it is applied (especially in Spring)
Spring Framework
- IoC/DI: searching for constructors/setters/fields annotated with (
@Autowired,@Value), and dependency injection through reflection. - AOP/Protocols: creating dynamic proxies:
- JDK Proxy for interfaces;
- CGLIB for classes (creates a subclass at runtime).
- Annotation Processing:
@Transactional,@Cacheable,@RequestMappingand others — reading metadata and building advice/interceptor chains. - Data Binding/Validation: binding fields from HTTP requests to beans (
WebDataBinder), reading constraints@NotNull,@Size.
Other examples:
- Hibernate/JPA — access to private fields/methods, entity mapping.
- Jackson/Gson — serialization/deserialization by fields/getters.
- Bean Validation (Jakarta Validation) — searching for constraint annotations.
- JUnit/Mockito — searching for test methods, creating mocks/stubs.
Basic Operations — Code Examples
1. Get Class<?>
Class<?> c1 = String.class;
Class<?> c2 = Class.forName("com.example.User");
Object obj = new User();
Class<?> c3 = obj.getClass();2. Fields
class User {
private String name;
public int age;
}
// Reading/writing a field
User u = new User();
Field f = User.class.getDeclaredField("name");
f.setAccessible(true); // Java 8; in Java 9+ there may be module restrictions
f.set(u, "Alice"); // write to a private field
String name = (String) f.get(u); // read3. Methods
class Calculator {
private int add(int a, int b) { return a + b; }
}
Calculator calc = new Calculator();
Method m = Calculator.class.getDeclaredMethod("add", int.class, int.class);
m.setAccessible(true);
int sum = (int) m.invoke(calc, 2, 3); // 54. Constructors
class Person {
private final String name;
private Person(String name) { this.name = name; }
}
Constructor<Person> ctor = Person.class.getDeclaredConstructor(String.class);
ctor.setAccessible(true);
Person p = ctor.newInstance("Bob");5. Annotations
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Column { String name(); }
class Entity {
@Column(name = "user_name")
private String username;
}
Field f = Entity.class.getDeclaredField("username");
Column col = f.getAnnotation(Column.class);
if (col != null) {
System.out.println(col.name()); // "user_name"
}6. Working with types and generic metadata
class Repo<T> {
List<T> items;
}
Field items = Repo.class.getDeclaredField("items");
Type gType = items.getGenericType(); // java.util.List<T>
if (gType instanceof ParameterizedType pt) {
Type arg = pt.getActualTypeArguments()[0]; // T (erased at runtime, but visible as TypeVariable)
}7. Arrays via java.lang.reflect.Array
Object arr = Array.newInstance(String.class, 3);
Array.set(arr, 0, "a");
String first = (String) Array.get(arr, 0);8. Dynamic Proxy (JDK Proxy)
interface Greeting {
String hello(String name);
}
class LoggingHandler implements InvocationHandler {
private final Object target;
LoggingHandler(Object target) { this.target = target; }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">>> " + method.getName());
return method.invoke(target, args);
}
}
Greeting original = name -> "Hi " + name;
Greeting proxy = (Greeting) Proxy.newProxyInstance(
Greeting.class.getClassLoader(),
new Class[]{Greeting.class},
new LoggingHandler(original)
);
System.out.println(proxy.hello("Alice")); // log + "Hi Alice"9. Fast invocation via MethodHandles/VarHandle
class A { private int x = 42; }
A a = new A();
MethodHandles.Lookup lookup = MethodHandles.lookup();
VarHandle vh = MethodHandles.privateLookupIn(A.class, lookup)
.findVarHandle(A.class, "x", int.class);
int v = (int) vh.get(a); // 42
vh.set(a, 100); // faster than Field.set in hot codeModular System (Java 9+) and Accessibility
- In the era of
--add-exports/--add-opensaccess to non-public members can be prohibited if the module hasn’t opened the package. - In Spring Boot, the JVM option is often used:
--add-opens java.base/java.lang=ALL-UNNAMED(or specific modules) to allow reflection to work with private members. - In production, it’s better to minimize “opens” and reflection to private.
Why use with caution
1. Violation of Encapsulation
setAccessible(true)/privateLookupIn allow changing private fields/methods.
This breaks the contract of the class, complicates maintenance, and can lead to brittle code.
2. Security
- Access to private data, bypassing invariants, potential leaks.
- In runtime (especially in plugin systems), malicious behavior can be injected.
3. Performance
- Reflection is slower than direct invocation; frequent
Method.invoke/Field.getin hot loops → performance degradation. - Dynamic proxies/class generation (CGLIB) → time and memory, JIT warm-up.
4. Compatibility and Brittleness
- Renaming/refactoring fields/methods breaks code that refers to them by string names.
- Module restrictions in Java 9+ (and different JDKs) can suddenly block access.
5. Type Erasure
- Generic types are erased → reflection doesn’t know the specific type parameter at runtime (sees
Listinstead ofList<String>), requiring tricks withType/ParameterizedType.
Typical Errors and How to Solve Them
| Error | Cause | Solution |
|---|---|---|
NoSuchFieldException / NoSuchMethodException | Incorrect names, signatures, modifiers | Check exact parameter types, use getDeclared* for non-public members |
IllegalAccessException | No access (modifier/module) | setAccessible(true) (Java 8) or privateLookupIn + --add-opens (Java 9+) |
InvocationTargetException | Exception from within the invoked method | Log getCause(), wrap in a domain exception |
ClassNotFoundException | Class not found by the specified ClassLoader | Check class name/CL, in web containers use TCCL (Thread Context ClassLoader) |
| Memory Leaks | Long-lived references to ClassLoader/proxies | Cache descriptors, don’t hold CL in static, close plugins correctly |
| Performance Degradation | Frequent reflective calls | Cache descriptors, use MethodHandle/VarHandle or direct invocation |
| Brittle code during refactoring | String names | Centralize name constants, minimize reflection points |
Best Practices (Quick Checklist)
- First, use regular code. Reflection is when there’s no other way (universal frameworks, cross-type tools).
- Minimize scope. Wrap in small utilities/adapters, don’t spread it throughout the code.
- Cache
Field/Method/Constructor. Don’t search for them again in every call. - Prefer
MethodHandles/VarHandlein hot spots — they are faster after warm-up. - Consider modules. In Java 9+, plan
--add-opensor an architecture without access to private members; design the API so it doesn’t require it. - Don’t break invariants. If you change a private field, document and test invariants.
- Security. Check the source of classes/plugins, don’t let reflection roam the entire ClassLoader.
- Log and wrap. All reflective exceptions — with context (which class/field/method), wrap in domain exceptions.
- For annotations — create a metadata layer, don’t read annotations everywhere.
- In Spring — rely on standard mechanisms (DI, converters, bean post-processors) as much as possible, and use reflection only in extension points.
Mini-FAQ for Interviewing
- What is the danger of reflection? Violates encapsulation, can break contracts, degrades performance and security; plus problems with modules.
- When to use it? Frameworks/universal libraries, dynamic plugins, serialization/mapping, DI/AOP.
- JDK Proxy vs CGLIB? JDK Proxy — only for interfaces; CGLIB — proxies classes (through inheritance), be careful with
final. Spring chooses. - MethodHandles/VarHandle? More modern and faster than reflection through
Method/Field, especially in hot code; but a more complex API. - How to work with Java 9+ modules? Use
--add-opens/--add-exports, avoid accessing private members if possible, design the API so it doesn’t require it.