RU | EN | DE

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)

  1. Class loaded by ClassLoader → its metadata is available through the Class<?> object.
  2. We get Fields/Methods/Constructors, annotations, modifiers through Class<?>.
  3. Using AccessibleObject#setAccessible(true) (Java 8) or canAccess/modular “opens” (Java 9+) we can access non-public members (with module system restrictions).
  4. We call Method.invoke, read/write Field.get/set, create an object through Constructor.newInstance.
  5. 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, @RequestMapping and 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);  // read

3. 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); // 5

4. 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 code

Modular System (Java 9+) and Accessibility

  • In the era of --add-exports/--add-opens access 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.get in 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 List instead of List<String>), requiring tricks with Type/ParameterizedType.

Typical Errors and How to Solve Them

ErrorCauseSolution
NoSuchFieldException / NoSuchMethodExceptionIncorrect names, signatures, modifiersCheck exact parameter types, use getDeclared* for non-public members
IllegalAccessExceptionNo access (modifier/module)setAccessible(true) (Java 8) or privateLookupIn + --add-opens (Java 9+)
InvocationTargetExceptionException from within the invoked methodLog getCause(), wrap in a domain exception
ClassNotFoundExceptionClass not found by the specified ClassLoaderCheck class name/CL, in web containers use TCCL (Thread Context ClassLoader)
Memory LeaksLong-lived references to ClassLoader/proxiesCache descriptors, don’t hold CL in static, close plugins correctly
Performance DegradationFrequent reflective callsCache descriptors, use MethodHandle/VarHandle or direct invocation
Brittle code during refactoringString namesCentralize name constants, minimize reflection points

Best Practices (Quick Checklist)

  1. First, use regular code. Reflection is when there’s no other way (universal frameworks, cross-type tools).
  2. Minimize scope. Wrap in small utilities/adapters, don’t spread it throughout the code.
  3. Cache Field/Method/Constructor. Don’t search for them again in every call.
  4. Prefer MethodHandles/VarHandle in hot spots — they are faster after warm-up.
  5. Consider modules. In Java 9+, plan --add-opens or an architecture without access to private members; design the API so it doesn’t require it.
  6. Don’t break invariants. If you change a private field, document and test invariants.
  7. Security. Check the source of classes/plugins, don’t let reflection roam the entire ClassLoader.
  8. Log and wrap. All reflective exceptions — with context (which class/field/method), wrap in domain exceptions.
  9. For annotations — create a metadata layer, don’t read annotations everywhere.
  10. 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.