Reflection API
Inspect and control classes, methods, and fields while your program is running. By the end you'll read fields, invoke methods by name, build objects without new , and understand why this powers Spring, Hibernate, and JUnit — and when to leave it alone.
Learn Reflection API in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
Before You Start
This is an advanced topic. You should be comfortable with classes and objects , access modifiers like private , and the basics of annotations . Reflection works at the JVM level, deliberately bypassing the compile-time checks those features rely on — so understanding the normal rules first makes it clear what reflection is breaking.
Real-World Analogy: Opening the Remote
💡 Analogy: Normally you use an object like a TV remote — you press the labelled buttons and never think about the circuit board inside. Reflection is prising the case open. Now you can see every chip and wire, read values the buttons never exposed, and even solder new connections at runtime. It's powerful, but you've voided the warranty: the manufacturer's safety checks no longer protect you, and it's easy to break something.
That's the trade-off in one sentence. Code written before your class even existed can work with it — that's how Spring creates your beans, Hibernate maps your entities, and JUnit finds your @Test methods. But you give up the compiler's protection, you pay a speed cost, and you reach past private walls that were there for a reason.
1️⃣ The Starting Point: a Class < ? > Object
Every reflection task begins by getting a Class object — a runtime description of a type. The angle-bracket version Class < ? > just means "a Class of some type I'm not naming". There are three ways to get one, and they all return the same object for a given type.
- obj.getClass() — when you already have an object .
- String.class — when you know the type at compile time.
- Class.forName("java.lang.String") — when the type is only a String (from a config file, plugin, or user input). This is the most dynamic, and the one frameworks lean on.
Once you hold a Class object, you can ask it questions: its name, its fields, its methods, its constructors, its annotations.
2️⃣ Inspect, Invoke, and Instantiate
With a Class object in hand you can do four core things. The example below does all four on a small User class:
- Inspect fields: getDeclaredFields() lists every field the class declares (including private ones).
- Read a value: field.get(obj) returns that field's value from a specific object.
- Invoke a method by name: clazz.getMethod("greet") then method.invoke(obj) is the dynamic equivalent of obj.greet() .
- Create an instance: constructor.newInstance(args...) builds an object with no new keyword.
Notice setAccessible(true) in the example. getDeclaredField / getDeclaredMethod can find private members, but the JVM still blocks access until you explicitly switch the check off. That single call is what lets the code read the secret field and call privateMethod() .
🎯 Your Turn #1: Read a Private Field
Fill in the three blanks so the program reads the private price field of a Product . You need the field's name as a String, a call to switch off the access check, and a read with the object passed in.
3️⃣ Reading Annotations at Runtime
This is reflection's killer feature. An annotation like @Test is just metadata attached to a method. If the annotation is declared @Retention(RetentionPolicy.RUNTIME) , reflection can see it while the program runs and react to it.
That's the entire idea behind JUnit: loop over a class's methods, keep only the ones where m.isAnnotationPresent(Test.class) is true, and invoke each. Spring does the same with @Component ; Jackson with @JsonProperty . The example below is a working 30-line test runner.
🎯 Your Turn #2: Invoke a Method by Name
Fill in the blanks so the program looks up the hello method (which takes one String ) and calls it on object g with the argument "World" .
4️⃣ Core Reflection Operations at a Glance
Goal
API
Notes
Get class by name
Class.forName("pkg.MyClass")
Fully-qualified name
List fields
getDeclaredFields()
Incl. private, not inherited
List methods
getDeclaredMethods()
Read a field
field.get(obj)
May need setAccessible(true)
Call a method
method.invoke(obj, args)
Dispatch by string name
Build an object
constructor.newInstance(args)
No new keyword
Check annotation
m.isAnnotationPresent(X.class)
Needs RUNTIME retention
🧩 Mini-Challenge: Call a Private Method
Now with the scaffolding removed. You're given an Account whose deposit method is private . Using only the comment outline below, reflectively call deposit(50.0) and print the new balance. Everything you need is in the worked examples above.
Common Errors & Pitfalls
- ❌ The performance cost: reflective calls are several times slower than direct calls (extra lookups, access checks, and argument boxing into Object[] ). Worse, never look members up inside a hot loop — cache the Method / Field objects once, and use MethodHandle for the hottest paths.
- ❌ Breaking encapsulation: setAccessible(true) reaches past private — the very wall that keeps a class's internals safe to change. Code that does this is brittle and easy to misuse; reach for public APIs first.
- ❌ Fragile to refactors: getMethod("greet") uses a String, so the compiler can't catch a typo or a rename. NoSuchMethodException or NoSuchFieldException appears only at runtime, often long after the rename that caused it.
- ❌ Security & the module system (JPMS): on modern Java, setAccessible on code in another module throws InaccessibleObjectException unless that module opens the package. A SecurityManager can also veto reflective access entirely.
- ❌ Swallowing the wrapped exception: when an invoked method throws, you get InvocationTargetException , not the original. Always unwrap with e.getCause() or your real error is hidden.
- ❌ Forgetting RUNTIME retention: an annotation with the default CLASS retention is invisible to reflection. Mark it @Retention(RetentionPolicy.RUNTIME) or isAnnotationPresent silently returns false.
Pro Tips
💡 Cache everything. Look up Method and Field objects once at startup and reuse them — the lookup is the expensive part, not the invoke.
💡 Prefer MethodHandle ( java.lang.invoke , Java 7+) for performance-sensitive reflective calls — it's JIT-friendly and far closer to direct-call speed.
💡 Prefer compile-time tools like annotation processors (Lombok, MapStruct) when you can — they generate ordinary code with zero runtime reflection cost.
💡 Proxy.newProxyInstance() builds an interface implementation at runtime — the foundation of Spring AOP and dynamic mocks.
📋 Quick Reference
Action
Syntax
Class from object
obj.getClass()
You have an instance
Class from type
String.class
Known at compile time
Class from name
Class.forName("pkg.X")
Most dynamic
Read field
f.get(obj)
setAccessible(true) if private
Invoke method
m.invoke(obj, args)
Returns Object
New instance
ctor.newInstance(args)
isAnnotationPresent(X.class)
Dynamic proxy
Proxy.newProxyInstance(...)
Runtime interface impl
Frequently Asked Questions
🎉 Lesson Complete!
You can now get a Class object three ways, inspect and read fields, invoke methods by name, build objects without new , and react to annotations at runtime — the exact machinery inside Spring, Hibernate, and JUnit. Just as importantly, you know its costs: it's slower, it breaks encapsulation, and it's fragile to refactors, so you reach for it only when you truly can't know the types in advance.
Next up: Annotations — how to create your own metadata and process it, the perfect companion to the reflection you just learned.
Practice quiz
Which way of getting a Class object works when the type name is only a String at runtime?
- obj.getClass()
- String.class
- Class.forName("java.lang.String")
- new String().class
Answer: Class.forName("java.lang.String"). Class.forName("...") loads a class by its fully qualified name — the most dynamic option, used by frameworks.
Do obj.getClass(), String.class, and Class.forName("java.lang.String") return the same Class object?
- Yes, all three are the identical Class instance for that type
- No, three different objects
- Only the first two are equal
- Only at compile time
Answer: Yes, all three are the identical Class instance for that type. There is one Class object per type, so all three resolve to the same instance (c1 == c2 == c3).
Which method lists every field a class declares, including private ones?
- getFields()
- getPublicFields()
- fields()
- getDeclaredFields()
Answer: getDeclaredFields(). getDeclaredFields() returns all fields declared by the class (including private); getFields() returns only public ones.
After getDeclaredField("price") on a private field, what must you call before reading it?
- field.open()
- field.setAccessible(true)
- field.unlock()
- field.makePublic()
Answer: field.setAccessible(true). getDeclaredField finds the field, but the JVM blocks access until you call setAccessible(true).
How do you read a field's value from a specific object?
- field.get(obj)
- field.read(obj)
- field.value()
- field.invoke(obj)
Answer: field.get(obj). field.get(obj) returns that field's value from the given object.
Which call is the reflective equivalent of obj.greet()?
- clazz.callMethod("greet", obj)
- obj.reflect("greet")
- clazz.getMethod("greet").invoke(obj)
- Method.run(obj, "greet")
Answer: clazz.getMethod("greet").invoke(obj). Look up the Method with getMethod("greet"), then call method.invoke(obj) to dispatch it dynamically.
How do you create an object without the 'new' keyword via reflection?
- clazz.create()
- constructor.newInstance(args)
- clazz.makeNew()
- Method.invoke(null)
Answer: constructor.newInstance(args). Get a Constructor (e.g. getDeclaredConstructor(...)) and call newInstance(args) to build the object.
For reflection to see an annotation at runtime, what retention must it have?
- RetentionPolicy.SOURCE
- RetentionPolicy.CLASS
- Any retention works
- RetentionPolicy.RUNTIME
Answer: RetentionPolicy.RUNTIME. Only @Retention(RetentionPolicy.RUNTIME) annotations are visible to reflection; the default CLASS retention is not.
When a reflectively-invoked method throws, what exception do you catch, and how do you get the real cause?
- RuntimeException; getMessage()
- InvocationTargetException; e.getCause()
- ReflectiveOperationException; e.toString()
- IllegalAccessException; e.getStackTrace()
Answer: InvocationTargetException; e.getCause(). invoke wraps the real error in InvocationTargetException; unwrap it with e.getCause() to see the original exception.
Which is a real downside of reflection?
- It is type-safe at compile time
- It cannot read private members
- It is slower, breaks encapsulation, and method-name typos only fail at runtime
- It only works on interfaces
Answer: It is slower, breaks encapsulation, and method-name typos only fail at runtime. Reflection is slower, bypasses compile-time checks (string names fail at runtime), and is fragile to refactors.
Continue this course
- Previous: Previous Lesson
- Next: Next Lesson