Reflection in Java
Inspecting and invoking fields, methods and constructors at runtime; setAccessible and its cost.
Code that reads code
The **java.lang.reflect API lets code inspect and use fields, methods and constructors** at runtime, without knowing them at compile time. Spring, Hibernate and JUnit are built on it: Field.get/set, Method.invoke, Constructor.newInstance.
Declared vs plain
**getDeclaredX returns everything declared in this exact class, any access level, but nothing inherited. getX returns only public members, including inherited** ones.
getDeclaredFields() // all, this class only
getFields() // public, incl. inherited
getDeclaredMethod("div", int.class, int.class)Picking the lock
What does this print?
class Vault { private int pin = 7; }
void main() throws Exception {
var f = Vault.class
.getDeclaredField("pin");
f.setAccessible(true);
var v = new Vault();
f.set(v, 9);
System.out.println(f.get(v));
}79Throws IllegalAccessException
Show the answer
getDeclaredField finds the private field, and setAccessible(true) lifts the access check for code in the same module. Reflection can read and write it: pin is now 9.
When the method throws
run() throws an IllegalStateException. What happens when it's called through reflection?
class Job {
void run() {
throw new IllegalStateException();
}
}
void main() throws Exception {
var m = Job.class.getDeclaredMethod("run");
m.invoke(new Job());
}Throws IllegalStateExceptionThrows InvocationTargetExceptionNothing: invoke swallows it
Show the answer
Method.invoke wraps whatever the target throws in an **InvocationTargetException**. The original IllegalStateException is its cause: call getCause() to get it.
setAccessible on the JDK
Since JDK 16/17, the JDK's internals are strongly encapsulated: java.base doesn't open its packages. setAccessible(true) on a private field of java.lang.String throws **InaccessibleObjectException**, unless the package is explicitly opened with --add-opens.
var f = String.class.getDeclaredField("value");
f.setAccessible(true);
// InaccessibleObjectExceptionKeep it for frameworks
In everyday business logic, reflection hurts: typos and type errors surface only at runtime, refactoring tools can't see string-based names, and each call does extra checks and boxing, so it's slower. Leave it to frameworks and tooling, where the flexibility pays off.
Key takeaways
- getDeclaredX: everything declared here; getX: public, incl. inherited
- Field.get/set, Method.invoke, Constructor.newInstance
- Exceptions from invoke arrive wrapped in InvocationTargetException
- Typos and type errors surface only at runtime
💡 Reflection is a master key and a building plan: powerful for the caretaker, risky for everyday use.
Since JDK 18 (JEP 416), core reflection like Method.invoke is implemented on top of method handles, which you'll meet two lessons from now.
Practice questions
What does this print?
class Safe {
private String code = "1234";
}
void main() throws Exception {
var f = Safe.class
.getDeclaredField("code");
f.setAccessible(true);
System.out.println(f.get(new Safe()));
}- null
- 1234
- Throws IllegalAccessException
- Compile error
Check your answer
1234. getDeclaredField finds private fields, and setAccessible(true) lifts the access check for code in the same (unnamed) module, so the value can be read.
What does this print?
class Calc {
int div(int a, int b) { return a / b; }
}
void main() throws Exception {
var m = Calc.class.getDeclaredMethod(
"div", int.class, int.class);
Object r = m.invoke(new Calc(), 1, 0);
System.out.println(r);
}- 0
- Throws IllegalAccessException
- Throws InvocationTargetException
- Throws ArithmeticException
Check your answer
Throws InvocationTargetException. Method.invoke wraps any exception thrown by the target method in InvocationTargetException; the ArithmeticException is its cause.