Skip to main content

    Lesson 31 • Advanced

    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.

    What You'll Learn in This Lesson

    • Get a Class<?> object via getClass(), .class, and Class.forName()
    • Inspect fields, methods, and constructors at runtime
    • Read and write private members with setAccessible(true)
    • Invoke methods dynamically by their string name
    • Create instances without the new keyword
    • Read annotations at runtime — and when NOT to use reflection

    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.

    Worked Example: Three Ways to a Class Object
    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
        }
    }
    Output
    c1 = java.lang.String
    c2 = java.lang.String
    c3 = java.lang.String
    All equal? true
    Simple name: String
    Is interface? false
    This is real code — run it for free atonecompiler.com/javaor in your own editor.

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

    Worked Example: Inspect Fields, Invoke Methods, Build Objects
    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());
        }
    }
    Output
    FIELDS:
      name = Alice (String)
      age = 30 (int)
      secret = hidden123 (String)
    greet() -> Hi, I'm Alice
    privateMethod() -> secret: hidden123
    new instance -> Hi, I'm Bob
    This is real code — run it for free atonecompiler.com/javaor in your own editor.

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

    Fill in the blanks (___)
    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
        }
    }
    This is real code — run it for free atonecompiler.com/javaor in your own editor.

    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.

    Worked Example: A Mini JUnit (Annotation-Driven 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");
        }
    }
    Output
    PASS  addsUp
    PASS  lengthOk
    FAIL  willFail: this one is meant to fail
    Results: 2 passed, 1 failed
    This is real code — run it for free atonecompiler.com/javaor in your own editor.

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

    Fill in the blanks (___)
    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!
        }
    }
    This is real code — run it for free atonecompiler.com/javaor in your own editor.

    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.

    Comment outline only — you write the code
    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
        }
    }
    This is real code — run it for free atonecompiler.com/javaor in your own editor.

    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

    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

    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.

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

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service