Reflection API

Reviewed & published by Brayan K

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.

Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, 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.

Once you hold a Class object, you can ask it questions: its name, its fields, its methods, its constructors, its annotations.

public class Main {
    public static void main(String[] args) throws Exception {
        String text = "hello";

        // Three ways to get the same Class object:

        // 1) From an existing object — .getClass()
        Class<?> c1 = text.getClass();        // you HAVE an object

        // 2) From the type itself — .class literal
        Class<?> c2 = String.class;           // you KNOW the type at compile time

        // 3) From a name — Class.forName(...)  (most dynamic)
        Class<?> c3 = Class.forName("java.lang.String"); // type is a String!

        System.out.println("c1 = " + c1.getName());   // java.lang.String
        System.out.println("c2 = " + c2.getName());   // java.lang.String
        System.out.println("c3 = " + c3.getName());   // java.lang.String

        // All three describe the SAME class, so they are identical:
        System.out.println("All equal? " + (c1 == c2 && c2 == c3)); // true

        // Handy questions a Class object can answer:
        System.out.println("Simple name: " + c1.getSimpleName());   // String
        System.out.println("Is interface? " + c1.isInterface());    // false
    }
}

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:

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().

import java.lang.reflect.*;

public class Main {
    static class User {
        public String name;
        public int age;
        private String secret = "hidden123";

        public User(String name, int age) { this.name = name; this.age = age; }
        public String greet() { return "Hi, I'm " + name; }
        private String privateMethod() { return "secret: " + secret; }
    }

    public static void main(String[] args) throws Exception {
        User user = new User("Alice", 30);
        Class<?> clazz = user.getClass();        // the User class, at runtime

        // 1) List the fields this class declares
        System.out.println("FIELDS:");
        for (Field f : clazz.getDeclaredFields()) {
            f.setAccessible(true);               // allow reading private fields too
            System.out.println("  " + f.getName()
                    + " = " + f.get(user)        // f.get(user) reads the value
                    + " (" + f.getType().getSimpleName() + ")");
        }

        // 2) Invoke a public method BY NAME (string -> behaviour)
        Method greet = clazz.getMethod("greet"); // look up "greet" with no args
        Object result = greet.invoke(user);      // same as user.greet()
        System.out.println("greet() -> " + result);

        // 3) Reach a PRIVATE method — encapsulation bypassed
        Method priv = clazz.getDeclaredMethod("privateMethod");
        priv.setAccessible(true);                // turn off the access check
        System.out.println("privateMethod() -> " + priv.invoke(user));

        // 4) Create a NEW object with no 'new' keyword
        Constructor<?> ctor = clazz.getDeclaredConstructor(String.class, int.class);
        User bob = (User) ctor.newInstance("Bob", 25); // -> new User("Bob", 25)
        System.out.println("new instance -> " + bob.greet());
    }
}

🎯 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.

import java.lang.reflect.*;

public class Main {
    static class Product {
        public String title = "Keyboard";
        private double price = 49.99;     // private on purpose
    }

    public static void main(String[] args) throws Exception {
        // 🎯 YOUR TURN — fill in the blanks marked with ___
        Product p = new Product();
        Class<?> clazz = p.getClass();

        // 1) Look up the PRIVATE field called "price"
        Field priceField = clazz.getDeclaredField(___);   // 👉 the field name in "quotes"

        // 2) Turn off the access check so you can read a private field
        priceField.___;                                    // 👉 setAccessible(true)

        // 3) Read its value out of object p
        Object value = priceField.___;                     // 👉 get(p)

        System.out.println("price = " + value);

        // ✅ Expected output: price = 49.99
    }
}

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.

import java.lang.annotation.*;
import java.lang.reflect.*;

public class Main {
    // A custom annotation, kept at RUNTIME so reflection can see it.
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @interface Test {}

    static class MathTests {
        @Test public void addsUp()    { if (2 + 2 != 4) throw new AssertionError("math broke"); }
        @Test public void lengthOk()  { if ("hi".length() != 2) throw new AssertionError("len broke"); }
        @Test public void willFail()  { throw new AssertionError("this one is meant to fail"); }
        public void notATest()        { throw new RuntimeException("never runs"); } // no @Test
    }

    public static void main(String[] args) throws Exception {
        Class<?> tests = MathTests.class;
        Object instance = tests.getDeclaredConstructor().newInstance();
        int passed = 0, failed = 0;

        for (Method m : tests.getDeclaredMethods()) {
            // Only run methods annotated with @Test — that's the whole trick.
            if (!m.isAnnotationPresent(Test.class)) continue;
            try {
                m.invoke(instance);
                passed++;
                System.out.println("PASS  " + m.getName());
            } catch (InvocationTargetException e) {
                // The real exception is wrapped — unwrap with getCause()
                failed++;
                System.out.println("FAIL  " + m.getName() + ": " + e.getCause().getMessage());
            }
        }
        System.out.println("Results: " + passed + " passed, " + failed + " failed");
    }
}

🎯 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".

import java.lang.reflect.*;

public class Main {
    static class Greeter {
        public String hello(String who) { return "Hello, " + who + "!"; }
    }

    public static void main(String[] args) throws Exception {
        // 🎯 YOUR TURN — fill in the blanks marked with ___
        Greeter g = new Greeter();
        Class<?> clazz = g.getClass();

        // 1) Look up the method "hello" that takes one String parameter
        Method m = clazz.getMethod(___, String.class);   // 👉 the method name in "quotes"

        // 2) Invoke it on object g, passing "World" as the argument
        Object result = m.invoke(___, ___);              // 👉 the object, then "World"

        System.out.println(result);

        // ✅ Expected output: Hello, World!
    }
}

4️⃣ Core Reflection Operations at a Glance

GoalAPINotes
Get class by nameClass.forName("pkg.MyClass")Fully-qualified name
List fieldsgetDeclaredFields()Incl. private, not inherited
List methodsgetDeclaredMethods()Incl. private, not inherited
Read a fieldfield.get(obj)May need setAccessible(true)
Call a methodmethod.invoke(obj, args)Dispatch by string name
Build an objectconstructor.newInstance(args)No new keyword
Check annotationm.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.

import java.lang.reflect.*;

public class Main {
    static class Account {
        private double balance = 100.0;
        private void deposit(double amount) { balance += amount; }
        public double getBalance() { return balance; }
    }

    public static void main(String[] args) throws Exception {
        // 🎯 MINI-CHALLENGE: Move money without calling deposit() directly
        // 1. Make an Account and get its Class object (account.getClass())
        // 2. Look up the PRIVATE method "deposit" — it takes one double
        //    (getDeclaredMethod("deposit", double.class))
        // 3. setAccessible(true), then invoke it with 50.0 on your account
        // 4. Print the balance with getBalance()
        //
        // ✅ Expected output: Balance: 150.0

        // your code here
    }
}

Common Errors & Pitfalls

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

ActionSyntaxNotes
Class from objectobj.getClass()You have an instance
Class from typeString.classKnown at compile time
Class from nameClass.forName("pkg.X")Most dynamic
Read fieldf.get(obj)setAccessible(true) if private
Invoke methodm.invoke(obj, args)Returns Object
New instancector.newInstance(args)No new keyword
Check annotationisAnnotationPresent(X.class)Needs RUNTIME retention
Dynamic proxyProxy.newProxyInstance(...)Runtime interface impl

🎉 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.

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

Frequently asked questions

What is reflection in Java?

Reflection is an API (in java.lang.reflect) that lets a running program inspect and manipulate classes, fields, methods, and constructors by name at runtime — instead of referring to them directly in code. It is how frameworks like Spring, Hibernate, and JUnit work with classes they never saw when they were compiled.

What is the difference between getClass(), .class, and Class.forName()?

All three return the same Class object. Use object.getClass() when you already have an instance, Type.class when you know the type at compile time, and Class.forName("fully.qualified.Name") when the class name only exists as a String (for example loaded from a config file or plugin).

What does setAccessible(true) do, and is it safe?

setAccessible(true) turns off Java's access check so you can read or invoke private members. It is essential for frameworks but breaks encapsulation, can be blocked by the Java module system or a SecurityManager, and may stop working if the class is refactored. Avoid it in ordinary application code — prefer public APIs.

Why is reflection slower than a normal method call?

Reflective calls do extra work every time — looking up members, checking access, boxing arguments into Object[] — and the JIT compiler cannot optimise them as aggressively as direct calls. Cache the Method and Field objects outside loops, and use MethodHandle (java.lang.invoke) for hot paths to reduce the cost.

When should I NOT use reflection?

Avoid reflection for everyday logic where interfaces, generics, or polymorphism would do the job. It is slower, bypasses compile-time type safety (typos in method names only fail at runtime), and is fragile when code is refactored. Reach for it only when you genuinely cannot know the types in advance — frameworks, serialization, plugins, and testing tools.

Related lessons