Calling a Private Method Using Reflection

Naveen AnamaneniUpdated · 7 min read

Normally, a private method can be called only from inside its own class. Reflection can get around this: calling setAccessible(true) on a Method, Field or Constructor object switches off Java's access check for that object, so private members can be called, read and changed from outside. This is a popular interview question, and it is how frameworks set private fields and how test tools reach internals. In this lesson, you will learn how it works, what it still can't do, how the module system limits it, and why you should rarely do it in your own code.

The Steps

  1. Get the Class object.
  2. Get the private member with a "Declared" method: getDeclaredMethod, getDeclaredField or getDeclaredConstructor. (getMethod sees only public members.)
  3. Call setAccessible(true) on it.
  4. Use it: invoke, get, set or newInstance.
MethodDescription
setAccessible(true)It is used to turn off access checks for this member object; throws InaccessibleObjectException if not permitted.
trySetAccessible()It does the same, but returns false instead of throwing.
canAccess(object)It tests whether the caller can use this member on the given object (pass null for static members).

Example: Private Method, Private Field, and the Limits

Priv.java▶ Try it
import java.lang.reflect.Field;
import java.lang.reflect.InaccessibleObjectException;
import java.lang.reflect.Method;
 
class Account {
    private double balance = 5000;
    private static final int LIMIT = 3;
 
    private String applyInterest(double rate) {
        balance += balance * rate / 100;
        return "new balance " + balance;
    }
}
 
public class Priv {
    public static void main(String[] args) throws Exception {
        Account account = new Account();
        Method m = Account.class
            .getDeclaredMethod("applyInterest", double.class);
        try {
            m.invoke(account, 10.0);
        } catch (IllegalAccessException e) {
            System.out.println("Refused: " + e.getMessage());
        }
 
        System.out.println("canAccess before: " + m.canAccess(account));
        m.setAccessible(true);
        System.out.println("canAccess after:  " + m.canAccess(account));
        System.out.println(m.invoke(account, 10.0));
 
        Field balance = Account.class.getDeclaredField("balance");
        balance.setAccessible(true);
        balance.set(account, 99.0);
        System.out.println("balance = " + balance.get(account));
 
        Field limit = Account.class.getDeclaredField("LIMIT");
        limit.setAccessible(true);
        try {
            limit.set(null, 10);
        } catch (IllegalAccessException e) {
            System.out.println("Refused: " + e.getMessage());
        }
 
        Field value = String.class.getDeclaredField("value");
        try {
            value.setAccessible(true);
        } catch (InaccessibleObjectException e) {
            String msg = e.getMessage();
            System.out.println("Refused: "
                + msg.substring(0, msg.indexOf(" to unnamed")));
        }
        System.out.println("trySetAccessible: "
            + value.trySetAccessible());
    }
}
Output
Refused: class Priv cannot access a member of class Account with modifiers "private"
canAccess before: false
canAccess after:  true
new balance 5500.0
balance = 99.0
Refused: Can not set static final int field Account.LIMIT to java.lang.Integer
Refused: Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang"
trySetAccessible: false

Explanation:

  1. Without setAccessible, invoke throws IllegalAccessException: finding a private member is allowed, using it isn't.
  2. After setAccessible(true), the private method runs, and the private field balance can be read and written. The flag belongs to this Method object only; the method itself is still private for everyone else.
  3. static final fields can't be changed, even with setAccessible(true).
  4. JDK internals are protected by modules. String's private value field belongs to the module java.base, which doesn't open the package java.lang to outside code, so setAccessible throws InaccessibleObjectException. trySetAccessible() reports the same thing by returning false.

What Reflection Still Can't Do

AttemptResult
Change a static final fieldIllegalAccessException
Change a field of a recordIllegalAccessException; record fields are truly final
Access private members of JDK classesInaccessibleObjectException, unless the package is opened
Access non-exported packages of a named moduleInaccessibleObjectException, unless the module opens the package
Create an enum constantIllegalArgumentException: Cannot reflectively create enum objects

A final instance field of an ordinary class can currently still be changed after setAccessible(true), but newer Java versions are moving to block this too, and the JIT may already have assumed the old value. Treat every final field as unchangeable.

Modules and Deep Reflection

Since Java 9, a module decides who may reflect into it. In module-info.java:

DeclarationEffect
exports com.school.api;Other modules may use its public types; no access to private members
opens com.school.model;Other modules may also use deep reflection (private members) at run time
opens com.school.model to com.fasterxml.jackson.databind;Opens it only to the named module
open module school { ... }Opens every package

This is why applications using Spring, Hibernate or Jackson with modules must opens their model packages. For JDK internals, the command-line option --add-opens does the same from outside. This program reads String's internal byte array:

Op.java
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
byte[] bytes = (byte[]) value.get("Hi");
System.out.println("String \"Hi\" is stored in " + bytes.length + " bytes");
Terminal
java --add-opens java.base/java.lang=ALL-UNNAMED Op.java
Terminal output
String "Hi" is stored in 2 bytes

Needing --add-opens is a sign that code depends on JDK internals that may change in any release.

Example: Breaking a Singleton

Private constructors are no protection against reflection either:

Single.java▶ Try it
import java.lang.reflect.Constructor;
 
class Registry {
    private static final Registry INSTANCE = new Registry();
 
    private Registry() { }
 
    static Registry get() {
        return INSTANCE;
    }
}
 
enum SafeRegistry { INSTANCE }
 
public class Single {
    public static void main(String[] args) throws Exception {
        Constructor<Registry> c = Registry.class.getDeclaredConstructor();
        c.setAccessible(true);
        Registry second = c.newInstance();
        System.out.println("same object? " + (second == Registry.get()));
 
        try {
            Constructor<?> e =
                SafeRegistry.class.getDeclaredConstructors()[0];
            e.setAccessible(true);
            e.newInstance("X", 1);
        } catch (IllegalArgumentException ex) {
            System.out.println("enum: " + ex.getMessage());
        }
    }
}
Output
same object? false
enum: Cannot reflectively create enum objects

Explanation: Reflection called the private constructor and made a second Registry, so the class is no longer a singleton. An enum can't be created reflectively, which is one reason "Thread-Safe Singleton in Java" recommends the enum singleton. A class-based singleton can defend itself by throwing an exception from the constructor if INSTANCE already exists.

Common Mistakes

Using getMethod for a private method. It throws NoSuchMethodException; use getDeclaredMethod.

Forgetting setAccessible(true). invoke throws IllegalAccessException.

Expecting it to work on JDK classes. Strong encapsulation blocks it unless the package is opened.

Assuming private is a security feature. It is a design tool; reflection and --add-opens can bypass it.

Interview Corner

How can you call a private method from outside its class?

With reflection: get the method with clazz.getDeclaredMethod(name, parameterTypes...), call setAccessible(true) on the returned Method, then invoke(target, args...). setAccessible(true) disables the access check for that Method object. It works for classes on the class path and for packages a module has opened; otherwise it throws InaccessibleObjectException. The same technique reads and writes private fields and calls private constructors.

Does reflection break encapsulation? How does Java limit it?

Yes, setAccessible(true) bypasses private. Java limits it in several ways: the module system (Java 9+) forbids deep reflection into packages that aren't opened, including JDK internals; static final fields and record fields can't be modified; enums can't be instantiated reflectively. Packages can be opened with opens in module-info.java or --add-opens on the command line.

Practice

  1. Easy: Write a class with a private method secretCode() that returns a number, and call it from another class with reflection.
  2. Harder: Protect Registry so its constructor throws IllegalStateException when an instance already exists, and confirm that the reflective attack now fails with InvocationTargetException.

Quick check: Private access with reflection

4 questions
1Which method finds a private method?
2What does setAccessible(true) do?
3What happens when you try to change a static final field?
4Why can't you access String's private value field by default?

Key Takeaways

Was this tutorial helpful?

Lesson 469 of 482