Reflection API
Java’s Reflection API gives your code the ability to examine itself — to look up class names, list methods and fields, invoke methods, and even tweak private members, all at runtime without knowing the types at compile time. It’s the engine behind dependency injection frameworks, testing tools, serialization libraries, and IDE auto-complete.
What Is Reflection?
Normally, Java is statically typed: you write String s = "hello" and the compiler knows everything about String before the program runs. Reflection breaks through that wall. Using the java.lang.reflect package together with the java.lang.Class object, your program can:
- Discover the name, superclass, and interfaces of any class
- List every constructor, method, and field — including
privateones - Invoke methods or access fields by name at runtime
- Create new instances without calling
newdirectly
This makes it incredibly powerful — and, if misused, a source of fragile, hard-to-debug code. Use it deliberately.
Note: Reflection is part of the
java.lang.reflectpackage. You don’t need a separate import forClassitself, since it lives injava.lang.
Getting a Class Object
Everything in Reflection starts with a Class<?> object. There are three ways to obtain one:
// 1. From a type literal (compile-time)
Class<String> c1 = String.class;
// 2. From an existing instance
String text = "hello";
Class<?> c2 = text.getClass();
// 3. By fully-qualified name (runtime string — most dynamic)
Class<?> c3 = Class.forName("java.util.ArrayList");
System.out.println(c1.getName()); // java.lang.String
System.out.println(c2.getSimpleName()); // String
System.out.println(c3.getSimpleName()); // ArrayList
Output:
java.lang.String
String
ArrayList
Class.forName() is especially useful when the class name comes from configuration, a database, or user input.
Inspecting Class Metadata
Once you have a Class<?>, you can interrogate it for all kinds of structural information.
import java.lang.reflect.*;
public class MetadataDemo {
public static void main(String[] args) {
Class<?> cls = java.util.LinkedList.class;
System.out.println("Name : " + cls.getName());
System.out.println("Superclass: " + cls.getSuperclass().getSimpleName());
System.out.print("Interfaces: ");
for (Class<?> iface : cls.getInterfaces()) {
System.out.print(iface.getSimpleName() + " ");
}
System.out.println();
System.out.println("Is abstract? " + Modifier.isAbstract(cls.getModifiers()));
}
}
Output:
Name : java.util.LinkedList
Superclass: AbstractSequentialList
Interfaces: List Deque Cloneable Serializable
Is abstract? false
The Modifier utility class converts the raw int returned by getModifiers() into readable boolean flags like isPublic(), isStatic(), isFinal(), etc.
Listing Methods and Fields
import java.lang.reflect.*;
public class MemberInspector {
public static void main(String[] args) {
Class<?> cls = String.class;
// getDeclaredMethods() — only this class, all access levels
// getMethods() — public methods from this class + superclasses
Method[] methods = cls.getDeclaredMethods();
System.out.println("Total declared methods in String: " + methods.length);
// Print a few fields
Field[] fields = cls.getDeclaredFields();
System.out.println("Declared fields in String:");
for (Field f : fields) {
System.out.println(" " + Modifier.toString(f.getModifiers())
+ " " + f.getType().getSimpleName()
+ " " + f.getName());
}
}
}
Output (Java 21, abbreviated):
Total declared methods in String: 86
Declared fields in String:
private final byte[] value
private final byte coder
private int hash
...
Tip: Prefer
getDeclaredXxx()when you want everything on the class itself. UsegetXxx()(without “Declared”) when you want only public, inherited members.
Invoking Methods Dynamically
The real power shows when you invoke a method by name at runtime:
import java.lang.reflect.*;
public class DynamicInvoke {
public static void main(String[] args) throws Exception {
String target = " hello world ";
// Look up the trim() method — no parameters, so empty Class array
Method trim = String.class.getMethod("trim");
String result = (String) trim.invoke(target);
System.out.println("'" + result + "'");
// Method with a parameter
Method toUpperCase = String.class.getMethod("substring", int.class);
String sub = (String) toUpperCase.invoke(target, 3);
System.out.println("'" + sub + "'");
}
}
Output:
'hello world'
'llo world '
method.invoke(instance, args...) calls the method on instance with the given arguments. For static methods, pass null as the first argument.
Creating Instances Without new
Reflection lets you instantiate a class when you only know its name as a string — a pattern frameworks like Spring use extensively. See Creating Objects Reflectively for the full story.
import java.lang.reflect.*;
public class ReflectiveNew {
public static void main(String[] args) throws Exception {
// Requires a no-arg constructor
Class<?> cls = Class.forName("java.util.ArrayList");
Object list = cls.getDeclaredConstructor().newInstance();
System.out.println(list.getClass().getSimpleName()); // ArrayList
// Constructor with arguments
Constructor<?> con = StringBuilder.class.getConstructor(String.class);
Object sb = con.newInstance("start-");
System.out.println(sb); // start-
}
}
Output:
ArrayList
start-
Note: The legacy
Class.newInstance()method (removed in Java 17 as best practice) has been replaced bygetDeclaredConstructor().newInstance(), which properly propagates checked exceptions.
Accessing Private Members
Reflection can bypass access control with setAccessible(true). This is explored in depth on Accessing Private Members, but here is a quick example:
import java.lang.reflect.*;
public class PrivateAccess {
private String secret = "classified";
}
class Demo {
public static void main(String[] args) throws Exception {
PrivateAccess obj = new PrivateAccess();
Field f = PrivateAccess.class.getDeclaredField("secret");
f.setAccessible(true); // override access check
System.out.println(f.get(obj)); // classified
f.set(obj, "revealed");
System.out.println(f.get(obj)); // revealed
}
}
Output:
classified
revealed
Warning:
setAccessible(true)bypasses encapsulation. Since Java 9, the module system may block it with anInaccessibleObjectExceptionunless the module explicitly opens the package. Never use this in production code on JDK internals — it can break with any JDK update.
Checking Annotations at Runtime
Reflection is also how annotation-processing libraries read metadata at runtime. Only annotations marked @Retention(RetentionPolicy.RUNTIME) are visible through reflection.
import java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Log {}
class Service {
@Log
public void process() { System.out.println("processing"); }
public void skip() {}
}
public class AnnotationCheck {
public static void main(String[] args) throws Exception {
for (Method m : Service.class.getDeclaredMethods()) {
if (m.isAnnotationPresent(Log.class)) {
System.out.println("@Log found on: " + m.getName());
m.invoke(new Service());
}
}
}
}
Output:
@Log found on: process
processing
This pattern is exactly how JUnit discovers @Test methods and how Spring finds @RequestMapping handlers.
Under the Hood
How Class Objects Work
Every class loaded by the JVM has exactly one Class<T> instance in the method area (part of the heap since Java 8’s removal of PermGen). That object holds a reference to the class’s bytecode metadata: the constant pool, field descriptors, method descriptors, and access flags. Reflection APIs read directly from this metadata.
Performance Cost
Reflective calls are significantly slower than direct calls:
| Operation | Approx. cost vs direct call |
|---|---|
method.invoke() (first call) | ~10–20× slower |
method.invoke() (after JIT warms up) | ~2–5× slower |
Field get/set | ~3–6× slower |
The JVM generates accessor code on demand and caches it after a threshold (controlled by sun.reflect.inflationThreshold, default 15). After that threshold, the JIT can optimize heavily, but the overhead never fully disappears because of argument boxing, varargs arrays, and access checks. In tight loops, prefer direct APIs or MethodHandle (available since Java 7) which compiles down to near-direct-call performance.
MethodHandle — The Modern Alternative
java.lang.invoke.MethodHandle (introduced in Java 7) offers a JIT-friendly, type-safe way to do many of the same things as reflection without the overhead. Lambdas and default methods internally use MethodHandles via invokedynamic bytecode. For new code where performance matters, prefer MethodHandle over Method.invoke().
Module System Restrictions (Java 9+)
The Java 9 module system added strong encapsulation. Calling setAccessible(true) on a member in an unexported or unopened package throws InaccessibleObjectException at runtime. To allow deep reflection, the module must declare opens <package> in its module-info.java. This is why you sometimes see --add-opens flags on the command line when running older frameworks on Java 11+.
In This Section
- Creating Objects Reflectively — Use
Constructor.newInstance()to instantiate classes at runtime from a string class name. - javap Tool — Disassemble
.classfiles to inspect bytecode, method signatures, and the constant pool from the command line. - Accessing Private Members — Use
setAccessible(true)to read and write private fields and invoke private methods, with module-system caveats.
Common Use Cases
- Dependency injection — Spring, Guice, and CDI scan the classpath, read annotations, and wire beans together without compile-time knowledge of implementations.
- Test frameworks — JUnit and TestNG discover and invoke
@Testmethods dynamically. - Serialization — Jackson, Gson, and Java’s own
ObjectInputStreamread field values to serialize/deserialize objects. - IDE tooling — Auto-complete, debuggers, and profilers interrogate class structure at runtime.
- ORM frameworks — Hibernate maps Java fields to database columns by reading field names and types reflectively.
Tip: If you catch yourself writing reflection in application code, ask whether an interface or a design pattern like Strategy would solve the problem more cleanly. Reflection is a powerful escape hatch, not a first resort.
Related Topics
- Annotations — Understanding
@Retentionand@Targetis essential for writing frameworks that use reflection to read metadata. - Interface — Many reflection patterns can be replaced with polymorphism; understand the trade-offs.
- Java 9: Modules (JPMS) — Module encapsulation directly affects what reflection can access; learn the
opensdirective. - Class Loaders & Class Loading — Reflection relies on the class loader hierarchy;
Class.forName()uses the calling class’s loader by default. - Creating Objects Reflectively — The next step: instantiate any class dynamically using
Constructor.newInstance(). - Design Patterns — Patterns like Factory and Proxy are often implemented with reflection under the hood.