Calling a Private Method Using Reflection
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
- Get the
Classobject. - Get the private member with a "Declared" method:
getDeclaredMethod,getDeclaredFieldorgetDeclaredConstructor. (getMethodsees only public members.) - Call
setAccessible(true)on it. - Use it:
invoke,get,setornewInstance.
| Method | Description |
|---|---|
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
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());
}
}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: falseExplanation:
- Without
setAccessible,invokethrowsIllegalAccessException: finding a private member is allowed, using it isn't. - After
setAccessible(true), the private method runs, and the private fieldbalancecan be read and written. The flag belongs to thisMethodobject only; the method itself is still private for everyone else. static finalfields can't be changed, even withsetAccessible(true).- JDK internals are protected by modules.
String's privatevaluefield belongs to the modulejava.base, which doesn't open the packagejava.langto outside code, sosetAccessiblethrowsInaccessibleObjectException.trySetAccessible()reports the same thing by returningfalse.
What Reflection Still Can't Do
| Attempt | Result |
|---|---|
Change a static final field | IllegalAccessException |
| Change a field of a record | IllegalAccessException; record fields are truly final |
| Access private members of JDK classes | InaccessibleObjectException, unless the package is opened |
| Access non-exported packages of a named module | InaccessibleObjectException, unless the module opens the package |
| Create an enum constant | IllegalArgumentException: 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:
| Declaration | Effect |
|---|---|
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:
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");java --add-opens java.base/java.lang=ALL-UNNAMED Op.javaString "Hi" is stored in 2 bytesNeeding --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:
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());
}
}
}same object? false
enum: Cannot reflectively create enum objectsExplanation: 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
- Easy: Write a class with a private method
secretCode()that returns a number, and call it from another class with reflection. - Harder: Protect
Registryso its constructor throwsIllegalStateExceptionwhen an instance already exists, and confirm that the reflective attack now fails withInvocationTargetException.
Key Takeaways
Was this tutorial helpful?