By WebNest Studio Engineering Team
Advanced Java Tutorial
Reflection API
Reflection is Java's ability to examine and manipulate classes, fields, methods, and constructors at runtime — even ones the code calling reflection did not know about at compile time. The core classes live in java.lang.reflect, and the entry point is almost always a Class object obtained through obj.getClass(), SomeType.class, or Class.forName("fully.qualified.Name").
Reflection is what makes frameworks possible: dependency injection containers (Spring), serialization libraries (Jackson, Gson), testing frameworks (JUnit), and ORMs (Hibernate) all use reflection to discover annotated fields and methods on classes they were never compiled against, and to construct or populate objects generically. It is powerful, but it comes with real performance and safety trade-offs that advanced Java developers need to understand before reaching for it.
Why it matters
Spring creates your beans and injects their dependencies, JUnit finds your test methods, and Jackson and Hibernate fill in private fields, all without knowing your classes in advance. They do it through reflection. Understanding it removes the mystery from how frameworks work and from the stack traces they produce.
Inspecting a Class at Runtime
Once you have a Class object, you can query almost everything about the type: its fields with getDeclaredFields(), its methods with getDeclaredMethods(), its constructors with getDeclaredConstructors(), its superclass, its interfaces, and its annotations. The "declared" variants return members defined directly on that class (including private ones), while the non-"declared" variants (getFields(), getMethods()) return only public members, including inherited ones.
Class.forName("com.example.Foo")— loads a class by name and returns its Class object.clazz.getDeclaredFields()/getDeclaredMethods()— all members declared on that class, regardless of access modifier.field.setAccessible(true)— bypasses Java's normal access checks so private members can be read or invoked.method.invoke(instance, args...)— calls a method reflectively, given a target object and its arguments.
Invoking Private Members
Normally, the compiler blocks access to private fields and methods from outside their class. Reflection can bypass this using setAccessible(true) on a Field, Method, or Constructor object, provided the JVM's module and security settings allow it. This is exactly how libraries like Gson populate private fields directly during deserialization, and how test frameworks call private helper methods to unit-test internal logic without exposing it publicly.
Performance and Safety Caveats
Reflective calls are noticeably slower than direct calls, because the JVM cannot fully optimize or inline a call it resolves by name at runtime, and each invocation goes through extra access checks and argument boxing. Reflection also weakens compile-time safety: typos in method or field names, or a mismatched argument type, only surface as a runtime NoSuchMethodException or IllegalArgumentException instead of a compile error. Since Java 9's module system, reflection into another module's internals can also be blocked entirely unless that module explicitly "opens" the package, which is a common source of runtime errors when upgrading legacy reflective code.
Example
Inspecting fields and methods, then invoking a private method reflectively
import java.lang.reflect.*;
import java.util.*;
class Account {
private String owner;
private double balance;
public Account(String owner, double balance) {
this.owner = owner;
this.balance = balance;
}
private double applyInterest(double rate) {
return balance * rate;
}
}
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("Account");
Field[] fields = clazz.getDeclaredFields();
Arrays.sort(fields, Comparator.comparing(Field::getName));
System.out.println("Fields:");
for (Field field : fields) {
System.out.println(" " + field.getType().getSimpleName() + " " + field.getName());
}
System.out.println("Methods:");
for (Method method : clazz.getDeclaredMethods()) {
System.out.println(" " + method.getName());
}
Object account = clazz.getDeclaredConstructor(String.class, double.class)
.newInstance("Priya", 1000.0);
Method applyInterest = clazz.getDeclaredMethod("applyInterest", double.class);
applyInterest.setAccessible(true); // bypass private access for this demo
Object result = applyInterest.invoke(account, 0.05);
System.out.println("Interest: " + result);
}
}
Fields:
double balance
String owner
Methods:
applyInterest
Interest: 50.0
Common Mistakes
- Reaching for reflection to solve a problem that polymorphism or an interface would solve more safely and more efficiently.
- Forgetting setAccessible(true) before invoking a private member, resulting in an IllegalAccessException.
- Caching neither the Class nor the Method/Field object in a hot code path, forcing repeated (slow) reflective lookups by name.
- Assuming reflective access always works — Java 9+ module boundaries can block reflection into another module unless it is explicitly opened.
Exercise
Greeter has a private method. Using reflection, print how many methods the class declares, then make the private method accessible, invoke it on a new object with the argument Asha, and print the result.
Expected output: 1 then Hello, Asha
import java.lang.reflect.Method;
class Greeter {
private String greet(String name) {
return "Hello, " + name;
}
}
public class CallPrivate {
public static void main(String[] args) throws Exception {
Class<?> type = Greeter.class;
// TODO: print the number of declared methods
// TODO: look up "greet", make it accessible, invoke it with "Asha" and print the result
}
}
Show hints
getDeclaredMethod("greet", String.class)finds the method by name and parameter types.- Call
setAccessible(true), thenmethod.invoke(object, argument).
Show solution
import java.lang.reflect.Method;
class Greeter {
private String greet(String name) {
return "Hello, " + name;
}
}
public class CallPrivate {
public static void main(String[] args) throws Exception {
Class<?> type = Greeter.class;
System.out.println(type.getDeclaredMethods().length); // 1
Method method = type.getDeclaredMethod("greet", String.class);
method.setAccessible(true);
Object result = method.invoke(new Greeter(), "Asha");
System.out.println(result); // Hello, Asha
}
}
Quick quiz
1. Which call obtains the Class object for a class whose name is known only as a string?
- A. new Class(name)
- B. Class.load(name)
- C. Class.forName(name)
- D. Object.getClass(name)
Show answer
C. This is how frameworks load classes named in configuration.
2. What does getDeclaredFields() return?
- A. All fields declared in the class itself, of any visibility
- B. Only public fields, including inherited ones
- C. Only static fields
- D. The values of the fields
Show answer
A. getFields() is the one that returns public fields including inherited ones.
3. What happens when a method invoked through reflection throws an exception?
- A. It is swallowed
- B. It is rethrown unchanged
- C. The JVM exits
- D. It is wrapped in an InvocationTargetException
Show answer
D. The original exception is available from getCause().
Interview questions
How do frameworks such as Spring use reflection?
Show answer
At start-up the framework scans the classpath, loads classes and reads their annotations. It creates objects by calling constructors reflectively, injects dependencies by setting fields or calling constructors and setters, and invokes handler methods by name when a request arrives. None of this requires the framework to have been compiled against your classes, which is why adding an annotation is enough to make a class take part.
What are the drawbacks of reflection?
Show answer
It bypasses compile-time checking, so a misspelled method name or a wrong argument type fails only at runtime. It is slower than direct calls. It can break encapsulation by reaching private members, and under the module system such access is refused unless the package is opened. Code using it is harder to read and refactor, so in application code it is kept for cases that genuinely need it.
Key Points to Remember
- Reflection lets code inspect and invoke classes, fields, methods, and constructors it did not know about at compile time.
- Class.forName(), getDeclaredFields()/getDeclaredMethods(), and Method.invoke() are the core building blocks.
- setAccessible(true) bypasses private/protected access checks, which is how many frameworks read or set private state.
- Reflection trades compile-time safety and speed for runtime flexibility — use it for framework-style problems, not everyday logic.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
These examples need a JDK project with their libraries on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering