Skip to content
Chapter 18 · Core Java

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 reflection comes with real trade offs: it breaks encapsulation and it is slower than normal code. It is used rarely, but it is a very common interview topic.


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
public 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. The name is just a String, so you do not need the class at compile time.

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
// (Class.newInstance() also works but is deprecated since Java 9)
Object eagleObj = eagleClass.getDeclaredConstructor().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.


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


Why Reflection Is Used Rarely ​

Now that you have seen everything reflection can do, you might wonder why it is not used everywhere. There are three 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 is resolved at runtime: the JVM has to search for the method or field by the name you passed, check access, and check the argument types before making the call. That makes reflective code slower than a direct call, and also harder to read. If you already know the class, call its methods directly.

Check yourself ​

  1. What can reflection do?
  2. What is the Class object, who creates it, and what are the three ways to get it?
  3. What is the difference between getMethods() and getDeclaredMethods()? Why does getMethods() show wait() and notify()?
  4. How do you invoke a method by name through reflection?
  5. What happens if you set a private field through reflection without setAccessible(true)?
  6. How does reflection break the Singleton pattern, and how do you guard against it?
  7. Why is reflection rarely used in application code?
Answers
  1. Examine a class at runtime (its methods, fields, constructors, modifiers, interfaces) and change its behavior, for example by changing field values or calling methods by name.
  2. A java.lang.Class object that holds a class's metadata; the JVM creates one per loaded class. Class.forName("Eagle"), Eagle.class, or eagleObj.getClass().
  3. getMethods() returns public methods of the class and all its parents (so Object's public methods appear). getDeclaredMethods() returns all methods, public and private, declared in that class only.
  4. Get the Method with getMethod("fly", int.class, boolean.class, String.class), then call method.invoke(object, 1, true, "hello").
  5. IllegalAccessException: the calling class cannot access a private member.
  6. Get the private constructor with getDeclaredConstructors(), call setAccessible(true), then newInstance() to create a second object. Guard: in the private constructor, throw an exception if the instance already exists.
  7. It breaks encapsulation (private members become reachable), it can break Singleton, and it is slower than direct calls because everything is resolved at runtime.