Skip to content

Java Reflection in Depth

The Ability to Look in the Mirror

Imagine if your code could wake up at runtime, look at itself, and say: "What methods do I have? What are my fields? Who made me? Can I call myself by name?" That is exactly what Java reflection lets you do. It is the ability for a running Java program to examine its own structure and even change its own behavior while it is executing.

The word "examine" here is important. Reflection lets you inspect a class you might not even know about at compile time. You can ask a class: how many methods do you have? What are your fields? Are they public or private? What constructors exist? And once you have that information, you can actually use it. You can invoke a method by passing its name as a string. You can read or write a private field. You can call a private constructor. You can create new objects of any class just by knowing its name.

This sounds almost magical, and in a way it is. But before you get too excited, you should know that reflection comes with real trade offs. It breaks encapsulation, it is slower than normal code, and it bypasses the type safety that Java normally guarantees. Most experienced developers use it rarely, and only for very specific reasons. But understanding it thoroughly is essential, because it shows up constantly in interviews, and because the frameworks you use every day, including Spring, depend on it at their core.


What Reflection Actually Does

Reflection gives you four core capabilities. First, it lets you examine a class: find all its methods, all its fields, all its constructors, what interfaces it implements, what its modifiers are. Second, it gives you the metadata about each of those things: the return type of a method, the parameter types, the name, the declaring class. Third, it lets you invoke those methods at runtime, passing arguments just like a normal call would. Fourth, and most powerfully, it lets you change the value of fields, including private ones, during program execution.

When you change a field value this way, you are changing how the object behaves. An object's behavior depends on its state, and its state lives in its fields. So reflection gives you a backdoor into the internal state of any object.


The Class Object: The Gateway to Everything

To understand reflection, you need to understand one particular class: java.lang.Class. Yes, there is a class called Class. The name is a little confusing at first, but stay with it.

The JVM does something interesting every time it loads a class. Say your code references the Eagle class for the first time. The JVM loads the bytecode for Eagle, and at the same time, it automatically creates one object of type Class to represent Eagle. This Class object holds all the metadata about Eagle: its name, its methods, its fields, its constructors, its modifiers, everything. The JVM creates exactly one such Class object per loaded class. Two different variables pointing to the Class object for Eagle will both point to the same object in memory.

This Class object is your gateway to reflection. Once you have it, you can call methods like getMethods(), getDeclaredFields(), getConstructors(), getName(), getModifiers(), and many others. Without this object, you cannot do any reflection at all. So the first question in any reflection task is always: how do I get the Class object for the class I want to inspect?


Three Ways to Get a Class Object

There are exactly three ways, and each one suits a different situation.

java
class Eagle {
    public String breed;
    private boolean canSwim;

    public void fly() {
        System.out.println("flying");
    }

    private void eat() {
        System.out.println("eating");
    }
}

Way One: Class.forName()

java
// Pass the class name as a String
// Throws ClassNotFoundException if the name is wrong or the class is not on the classpath
Class<?> c1 = Class.forName("Eagle");

You pass the fully qualified class name as a string. The JVM looks it up and returns the corresponding Class object. If you spell the name wrong, or if the class does not exist on the classpath, you get a ClassNotFoundException. This approach is the most flexible because the name can come from anywhere: a configuration file, a database, user input, a plugin manifest. You do not need to know the class at compile time. This is how plugin systems and dependency injection frameworks work, reading class names from configuration and loading them dynamically.

Way Two: .class Literal

java
// Compile-time constant, no exception possible
Class<Eagle> c2 = Eagle.class;

You write the class name followed by .class. This is evaluated at compile time. No exception can be thrown because the compiler already knows the class exists. This is the cleanest and most common approach when you have access to the class at compile time.

Way Three: getClass() on an Object

java
// Works on any object; returns the actual runtime type
Eagle eagleObj = new Eagle();
Class<?> c3 = eagleObj.getClass();

Every object in Java inherits getClass() from Object. It returns the Class object for the actual runtime type of the object. This is useful when you have a variable typed as a parent class or interface, and you want to inspect the actual concrete type underneath.

All three of these return the same object. The JVM creates only one Class object per loaded class, so c1 == c2 == c3 evaluates to true. They are all pointing at the same thing.


Examining a Class: Name and Modifiers

Once you have the Class object, the simplest things you can ask are the class name and its modifier.

java
Class<Eagle> eagleClass = Eagle.class;

// getName() returns the fully qualified class name as a String
System.out.println(eagleClass.getName());       // prints: Eagle

// getModifiers() returns an int encoding the modifiers
// Use java.lang.reflect.Modifier to decode it
System.out.println(eagleClass.getModifiers());  // prints: 1 (which means public)

The modifiers come back as an integer because Java encodes access flags as bit fields. The Modifier class has static methods to decode them: Modifier.isPublic(mod), Modifier.isPrivate(mod), Modifier.isAbstract(mod), and so on.


Reflecting Methods: getMethods vs getDeclaredMethods

This is one of the most important distinctions in reflection, and one of the most common interview questions. The two methods sound similar but behave very differently.

java
Class<Eagle> eagleClass = Eagle.class;

getMethods()

java
// Returns all PUBLIC methods, including inherited ones from parent classes
for (java.lang.reflect.Method m : eagleClass.getMethods()) {
    System.out.println(
        m.getName() +
        " | return type: " + m.getReturnType() +
        " | declared in: " + m.getDeclaringClass()
    );
}

The output includes fly, but it also includes wait, notify, notifyAll, toString, equals, hashCode, and getClass. Where did all of those come from? They came from Object. Every class in Java implicitly extends Object, and getMethods() walks the entire inheritance chain, collecting every public method from every ancestor. So you get Object's public methods whether you want them or not.

Notice that eat does not appear at all, because eat is private.

getDeclaredMethods()

java
// Returns ALL methods declared in THIS class only (public and private), no inherited methods
for (java.lang.reflect.Method m : eagleClass.getDeclaredMethods()) {
    System.out.println(m.getName());
}
// Output: fly, eat

getDeclaredMethods() gives you only the methods that are literally written in the Eagle class itself. It does not go to parent classes. But it gives you everything in that class regardless of visibility: both the public fly and the private eat appear here.

The rule is simple. The get prefix (without Declared) means: go up the inheritance tree, but only collect public members. The getDeclared prefix means: stay in this class only, but collect everything regardless of visibility.

This same pattern applies to fields and constructors:

getFields() returns all public fields from this class and all its parents. getDeclaredFields() returns all fields declared in this class only, regardless of visibility. getConstructors() returns all public constructors of this class. getDeclaredConstructors() returns all constructors of this class, including private ones.

Getting Method Metadata

java
for (java.lang.reflect.Method m : eagleClass.getDeclaredMethods()) {
    System.out.println("Name: " + m.getName());
    System.out.println("Return type: " + m.getReturnType());
    System.out.println("Declared in: " + m.getDeclaringClass());
    System.out.println("Parameter count: " + m.getParameterCount());
    System.out.println("Modifiers: " + m.getModifiers());
}

Each Method object exposes its own metadata. You can find out the return type, which class declared the method, how many parameters it accepts and what their types are, what exceptions it declares, and what its access modifiers are. All of these classes live in java.lang.reflect.


Invoking a Method at Runtime

Reflection does not just let you look at methods. It lets you actually call them. This is one of the most powerful features and also one of the most dangerous if misused.

java
class Eagle {
    public void fly(int speed, boolean soaring, String direction) {
        System.out.println(
            "Flying at speed " + speed +
            ", soaring: " + soaring +
            ", direction: " + direction
        );
    }
}
java
// Step 1: Get the Class object for Eagle using forName
Class<?> eagleClass = Class.forName("Eagle");

// Step 2: Create an instance of Eagle using reflection
// newInstance() calls the no-argument constructor
Object eagleObj = eagleClass.newInstance();

// Step 3: Get the Method object by name and parameter types
// You must specify parameter types to distinguish overloaded methods
java.lang.reflect.Method flyMethod =
    eagleClass.getMethod("fly", int.class, boolean.class, String.class);

// Step 4: Invoke the method on the object, passing arguments
flyMethod.invoke(eagleObj, 1, true, "north");
// Output: Flying at speed 1, soaring: true, direction: north

The invoke call takes the object to call the method on, followed by the arguments. The method resolves at runtime. If you pass the wrong argument types, you get an IllegalArgumentException. If the method does not exist with that name and those parameter types, getMethod throws a NoSuchMethodException.

This is how frameworks like Spring discover your @Controller methods and call them when a request comes in. Spring does not know at compile time which of your methods handles which URL. It finds out at runtime through reflection.


Accessing and Modifying Fields

Reflection lets you read and write fields of an object, including fields you declared private.

Public Field

java
class Eagle {
    public String breed;
    private boolean canSwim;
}

Class<?> eagleClass = Eagle.class;
Object eagleObj = eagleClass.newInstance();

// Get the field by name
java.lang.reflect.Field breedField = eagleClass.getDeclaredField("breed");

// Set the value on the object
breedField.set(eagleObj, "golden eagle");

// Read it back through the object directly
System.out.println(((Eagle) eagleObj).breed);  // golden eagle

If you pass a field name that does not exist, you get a NoSuchFieldException. Make sure you spell it exactly right.

Private Field: The setAccessible Hack

If you try the same thing with canSwim without any extra step, Java throws an exception:

IllegalAccessException: class Main cannot access a member of class Eagle with modifiers 'private'

This makes sense. The whole point of private is that code outside the declaring class cannot touch it. The canSwim field was declared inside Eagle, so normally only code inside Eagle can read or write it.

But reflection has a backdoor:

java
java.lang.reflect.Field canSwimField = eagleClass.getDeclaredField("canSwim");

// This is the key line: it bypasses Java's access control for this field
canSwimField.setAccessible(true);

// Now you can set the private field from anywhere
canSwimField.set(eagleObj, true);
System.out.println("canSwim set successfully");

setAccessible(true) tells the JVM to suppress the access check for this particular field on this particular Field object. The field's declared modifier does not change. The canSwim field is still technically private. But this specific Field reference now bypasses the enforcement.

This is one of the most important things to understand about reflection. It does not change what private means. It creates a way to sidestep the enforcement of it.


Reflecting Constructors: Where Singleton Gets Broken

Constructors are also reflectable. And this is where things get really interesting for interviews, because reflection is one of the ways a Singleton pattern can be broken.

Consider this class:

java
class Eagle {
    private Eagle() {
        // private constructor: normally nothing outside Eagle can call this
    }

    public void fly() {
        System.out.println("flying");
    }
}

Normally, if you try new Eagle() from another class, the compiler refuses. Private constructor means private constructor. There is no way around it... unless you use reflection.

java
Class<Eagle> eagleClass = Eagle.class;

// getDeclaredConstructors() returns ALL constructors, including private ones
java.lang.reflect.Constructor<?>[] constructors = eagleClass.getDeclaredConstructors();

// There is one constructor. Get it.
java.lang.reflect.Constructor<?> privateConstructor = constructors[0];

// Check what modifier it has
System.out.println(privateConstructor.getModifiers());  // prints 2 (private)

// Unlock the private constructor
privateConstructor.setAccessible(true);

// Create a new instance by calling the private constructor
Eagle eagleObject = (Eagle) privateConstructor.newInstance();

// Call the method on this freshly created object
eagleObject.fly();  // prints: flying

This works. Even though the constructor is private, setAccessible(true) followed by newInstance() creates a real, fully functional Eagle object. This is exactly how reflection breaks the Singleton pattern.

In a Singleton, the whole point of the private constructor is to prevent anyone from creating a second instance. But as you just saw, reflection ignores that restriction. A caller can reach in, find the private constructor, unlock it, and create as many instances as they want.

Defending the Singleton Against Reflection

The standard defense is to add a guard inside the private constructor itself:

java
class DatabaseConnection {
    private static DatabaseConnection instance;

    private DatabaseConnection() {
        // If an instance already exists, someone is trying to cheat
        if (instance != null) {
            throw new IllegalStateException(
                "Instance already exists. Use getInstance() instead."
            );
        }
    }

    public static DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }
}

Now when reflection calls newInstance() after setAccessible(true), the constructor runs, finds that instance is not null, and throws an exception. The second object cannot be created.

There is an even stronger defense: use an enum for your Singleton. The JVM itself guarantees that each enum constant is instantiated exactly once, and it explicitly prevents setAccessible from working on enum constructors. Trying to call newInstance() on an enum constructor throws an exception. The JVM enforces this at a level below the reflection API.


Why Reflection Is Used Rarely

Now that you have seen everything reflection can do, you might wonder why it is not used everywhere. The answer comes down to three serious problems.

It Breaks Encapsulation

Encapsulation is one of the four pillars of object oriented programming. When you declare a field or method as private, you are making a deliberate design decision: this internal detail is not part of the public contract. Nothing outside this class should depend on it or modify it. Reflection ignores that completely. Anyone can reach into any private field and change it. The protection that private is supposed to provide becomes optional.

This matters because private fields are private for a reason. They might be in a specific state that the class's own methods carefully maintain. If outside code changes a private field directly, it can leave the object in an inconsistent state that the class's own logic was never designed to handle.

It Breaks Singleton

You saw this in detail. A core design pattern that the entire Java community relies on can be bypassed with a few lines of reflection code. Any class that relies on a private constructor for its invariants is potentially vulnerable.

It Is Slow

Normal method calls in Java are fast. The JVM compiles frequently called methods into native machine code through the JIT compiler. When you call a method directly, the JVM can inline it, optimize it, and run it in nanoseconds.

Reflection is different. Everything happens at runtime. When you call invoke, the JVM has to look up the method by name, perform security checks, verify argument types, and then make the call in a way that the JIT cannot easily optimize. The overhead is real and measurable. Reflection heavy code can be ten to one hundred times slower than the equivalent direct call. This is not a theoretical concern. In performance critical code, the difference is substantial.

It Loses Type Safety

Java's generic type system and compile time type checking exist to catch mistakes before the program runs. When you use reflection, you are operating with raw strings and Object references. The compiler cannot check whether the method you are calling actually exists on the class, or whether the arguments you are passing match the parameter types. These errors only show up at runtime, as exceptions, potentially in production.


When Reflection Is the Right Tool

Given all of those problems, why does reflection exist at all? Because there is a legitimate class of problems where it is genuinely the only practical solution: framework infrastructure.

Spring Framework uses reflection extensively. When your Spring application starts, Spring scans your classpath, finds every class annotated with @Component, @Service, @Controller, or @Repository, and instantiates them. It then looks at their constructors and fields annotated with @Autowired and injects the right dependencies. It finds every method annotated with @GetMapping or @PostMapping and registers them as HTTP request handlers. None of this is possible without reflection, because Spring does not know your classes at compile time. You write your classes; Spring discovers them at runtime.

Testing frameworks use it too. JUnit finds test methods by scanning for the @Test annotation through reflection. Mockito uses reflection to create mock implementations of interfaces and spy on method calls.

Serialization and deserialization libraries like Jackson use reflection to read field values when converting objects to JSON, and to write field values when converting JSON back into objects.

Plugin systems use Class.forName() to load classes from external JAR files based on names in a configuration file.

In all of these cases, the framework author genuinely does not know at compile time which classes will exist. The user of the framework provides those classes later. Reflection is how the framework reaches into those user provided classes and makes them work.

In your own application code, the situation is different. You know your classes. You wrote them. You should call their methods directly. Reaching for reflection in application code is usually a sign that something is wrong with the design. It adds complexity, removes type safety, and slows things down for no benefit.


Common Exceptions to Recognize

Reflection throws checked exceptions that you have to handle. You will see these in interview questions and in practice:

ClassNotFoundException is thrown when Class.forName() cannot find a class with the name you provided. Double check the spelling and make sure the class is on the classpath.

NoSuchMethodException is thrown when getMethod() cannot find a method with that name and those parameter types. Make sure you are passing the right parameter types in the right order.

NoSuchFieldException is thrown when getDeclaredField() cannot find a field with that name. Spelling must be exact.

IllegalAccessException is thrown when you try to access a private member without calling setAccessible(true) first. The error message will say something like "cannot access a member with modifiers 'private'".

InstantiationException is thrown when newInstance() cannot create an object, for example if the class is abstract or does not have a no argument constructor.


Interview Questions to Know Cold

Interviewers love reflection because it tests whether you understand the JVM deeply, not just the syntax.

The first question is: what is Java reflection? Answer: it is the ability for a running Java program to examine and modify its own structure at runtime. Through the java.lang.Class object and the java.lang.reflect package, you can inspect methods, fields, and constructors, invoke methods by name, read and write private fields, and call private constructors.

The second question is: what are the three ways to get a Class object? Answer: Class.forName("ClassName") when you have the name as a string, ClassName.class when you have the class available at compile time, and object.getClass() when you have an existing instance.

The third question is: what is the difference between getMethods() and getDeclaredMethods()? Answer: getMethods() returns all public methods of this class and all its ancestors. getDeclaredMethods() returns all methods of this class only, including private ones, but does not go to parent classes.

The fourth question is: why does getMethods() return wait(), notify(), and toString() when I call it on my class? Answer: because every class in Java implicitly extends Object, and those are public methods of Object. getMethods() walks the entire inheritance hierarchy.

The fifth question is: can reflection break the Singleton pattern? Answer: yes. You can use getDeclaredConstructors() to retrieve the private constructor, call setAccessible(true) on it, and then call newInstance() to create a new object. The defense is to check inside the private constructor whether an instance already exists and throw an exception if so. An even stronger defense is to implement Singleton using an enum.

The sixth question is: why is reflection slow? Answer: normal method calls can be optimized and inlined by the JIT compiler. Reflection calls must look up methods by name at runtime, perform security and type checks, and execute through a general purpose invocation mechanism that the JIT cannot optimize in the same way. The overhead makes reflection significantly slower than direct calls.

The seventh question is: what are legitimate use cases for reflection? Answer: framework infrastructure like Spring for dependency injection and request mapping, testing frameworks for discovering test methods via annotations, serialization libraries for reading and writing fields, and plugin systems that load classes by name from configuration.


A Complete Example to Practice

Run this yourself. Write the Eagle class, write a Main class, and work through each operation by hand. Reading about reflection is not the same as doing it.

java
// Eagle.java
public class Eagle {
    public String breed;
    private boolean canSwim;

    private Eagle() {
        System.out.println("Eagle constructed");
    }

    public void fly(int speed, boolean soaring, String direction) {
        System.out.println(
            "Flying at speed " + speed +
            ", soaring: " + soaring +
            ", direction: " + direction
        );
    }

    private void eat() {
        System.out.println("eating");
    }
}
java
import java.lang.reflect.*;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {

        // Get the Class object three ways
        Class<?> c1 = Class.forName("Eagle");
        Class<Eagle> c2 = Eagle.class;

        // All three point to the same object
        System.out.println("Same object: " + (c1 == c2));  // true

        // Class name and modifiers
        System.out.println("Class name: " + c2.getName());
        System.out.println("Modifiers: " + Modifier.toString(c2.getModifiers()));

        // getMethods: public methods including inherited from Object
        System.out.println("\n--- getMethods ---");
        for (Method m : c2.getMethods()) {
            System.out.println(m.getName() + " from " + m.getDeclaringClass().getSimpleName());
        }

        // getDeclaredMethods: all methods of Eagle only, public and private
        System.out.println("\n--- getDeclaredMethods ---");
        for (Method m : c2.getDeclaredMethods()) {
            System.out.println(m.getName() + " | " + Modifier.toString(m.getModifiers()));
        }

        // Create an object through reflection (using private constructor)
        Constructor<?> ctor = c2.getDeclaredConstructors()[0];
        ctor.setAccessible(true);
        Eagle eagleObj = (Eagle) ctor.newInstance();

        // Set a public field
        Field breedField = c2.getDeclaredField("breed");
        breedField.set(eagleObj, "golden eagle");
        System.out.println("\nbreed: " + eagleObj.breed);

        // Set a private field
        Field canSwimField = c2.getDeclaredField("canSwim");
        canSwimField.setAccessible(true);
        canSwimField.set(eagleObj, true);
        System.out.println("canSwim set to true via reflection");

        // Invoke the fly method
        Method flyMethod = c2.getMethod("fly", int.class, boolean.class, String.class);
        flyMethod.invoke(eagleObj, 120, true, "north");

        // Access the private eat method
        Method eatMethod = c2.getDeclaredMethod("eat");
        eatMethod.setAccessible(true);
        eatMethod.invoke(eagleObj);
    }
}

Work through every line. Ask yourself why each step is necessary. What happens if you skip setAccessible(true) before accessing the private field? What exception do you get? What happens if you call getMethod("eat") instead of getDeclaredMethod("eat")? Why does getMethods() return more results than getDeclaredMethods()? Getting your hands on these answers, by running the code and reading the exceptions, is what makes the concept permanent.


The Bottom Line

Reflection is powerful and dangerous in equal measure. It gives your code the ability to inspect and manipulate any class at runtime, bypassing access modifiers, invoking methods by string name, and creating objects of classes you do not know about until the program is running. These capabilities are what make frameworks like Spring, JUnit, and Jackson possible.

The cost is real though. Reflection breaks encapsulation, the principle that private things stay private. It breaks the Singleton pattern, since private constructors can be unlocked and called. It is slower than direct calls because everything resolves at runtime. And it loses the type safety that Java's compiler ordinarily provides.

Use reflection when you are building infrastructure that genuinely does not know what classes it will encounter at compile time. Avoid it in application code where you already know your classes and can call them directly. Know it well enough to explain it in an interview, to recognize when a framework is using it, and to understand why the experienced developers around you reach for it so rarely.