Reflection API
AdvancedThe Java Reflection API lets code inspect and manipulate classes, methods, and fields at runtime, enabling frameworks like Spring and JUnit.
Overview
Java reflection (java.lang.reflect) allows runtime inspection of classes, fields, methods, and constructors, and lets you invoke methods and access fields dynamically. Frameworks like Spring, Hibernate, JUnit, and Jackson all rely heavily on reflection for dependency injection, ORM mapping, test discovery, and JSON serialisation. While powerful, reflection bypasses compile-time type safety, is slower than direct calls, and can break with modules if encapsulation is not opened.
Inspecting Classes and Fields
Class<?> is the entry point for reflection. Obtain it via .class literal, getClass(), or Class.forName(). From a Class you can list fields, methods, constructors, annotations, and supertype information.
getDeclaredFields() returns all fields regardless of access; getFields() returns only public inherited ones. Same pattern for methods and constructors.
import java.lang.reflect.*;
Class<?> cls = User.class;
// Basic info
System.out.println(cls.getName()); // com.example.User
System.out.println(cls.getSimpleName()); // User
System.out.println(cls.getSuperclass()); // class java.lang.Object
// Fields
for (Field field : cls.getDeclaredFields()) {
System.out.printf(" %-20s [%s]%n",
field.getName(),
field.getType().getSimpleName());
}
// Access private field
Field nameField = cls.getDeclaredField("name");
nameField.setAccessible(true); // bypass access control
User user = new User("Alice", 30);
String name = (String) nameField.get(user);
System.out.println("Private name: " + name);Invoking Methods Dynamically
Method.invoke(object, args) calls a method dynamically. This is how JUnit discovers @Test methods and Spring calls controller handler methods.
For performance-critical paths, use MethodHandle (java.lang.invoke) which is faster than Method.invoke after JIT warm-up.
// Get and invoke a method by name
Class<?> cls = Calculator.class;
Calculator calc = new Calculator();
// Find method with specific parameter types
Method add = cls.getMethod("add", int.class, int.class);
int result = (int) add.invoke(calc, 3, 4); // 7
// Invoke private method
Method secret = cls.getDeclaredMethod("internalCalc", double.class);
secret.setAccessible(true);
double res = (double) secret.invoke(calc, 3.14);
// Create instance dynamically
Constructor<?> ctor = cls.getConstructor();
Object instance = ctor.newInstance();
// Process all @Test methods (JUnit-style)
for (Method method : cls.getDeclaredMethods()) {
if (method.isAnnotationPresent(Test.class)) {
method.invoke(instance);
System.out.println("Ran: " + method.getName());
}
}Generics and Reflection
Java erases generic type information at runtime (type erasure), but some type information is preserved in class/method signatures. getGenericType() returns the parameterised type for fields; getGenericParameterTypes() for methods.
This is how Jackson knows to deserialise List<User> — it reads the generic type from field signatures.
import java.lang.reflect.*;
// Accessing generic type info
Field field = MyClass.class.getDeclaredField("userList");
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType pt) {
Type[] typeArgs = pt.getActualTypeArguments();
System.out.println(typeArgs[0]); // class com.example.User
}
// Generic method parameter types
Method method = MyClass.class.getMethod("process", List.class);
Type[] paramTypes = method.getGenericParameterTypes();
if (paramTypes[0] instanceof ParameterizedType pt) {
System.out.println(pt.getActualTypeArguments()[0]);
}
// TypeToken pattern (used by Gson/Jackson)
// captures generic type at compile time via anonymous subclass
Type listUserType = new TypeReference<List<User>>(){}.getType();Key Points to Remember
- Class<?> is the entry point; obtain via .class, getClass(), or Class.forName().
- getDeclaredXxx() returns all members; getXxx() returns only public/inherited ones.
- setAccessible(true) bypasses private access — use cautiously; blocked by JPMS opens.
- Method.invoke() is slower than direct calls; use MethodHandle for performance-critical reflection.
- Generic types are erased at runtime but preserved in field/method signatures — accessible via getGenericType().
Practice Reflection API in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaWhat is the difference between getFields() and getDeclaredFields()?
What is type erasure and how does it affect reflection?
How does Spring use reflection for dependency injection?
Why is reflection slower than direct method calls?
What is the relationship between reflection and JPMS encapsulation?
Ask Aria about Reflection API
Your personal AI tutor — ask anything about this concept