skip to content

When does setAccessible(true) throw InaccessibleObjectException, and how do you fix it?

level: middleimportance: must knowfreq 60%

answer

  1. JPMS strong encapsulation gates deep reflection
  2. opens directive (module-info) for deep reflection; exports is for normal access
  3. --add-opens module/package=ALL-UNNAMED at launch
  4. java.base internals are sealed -> the classic break
  5. Unnamed module (classpath) mostly still works

basics

~20 s

Since Java 9, setAccessible(true) throws InaccessibleObjectException when the target type lives in a module whose package isn't opened for deep reflection. You fix it by opening that package — via an 'opens' directive in module-info or an --add-opens flag at launch.

solid answer

~50 s

Java 9 introduced the module system (JPMS) with 'strong encapsulation': by default a module's non-exported, non-opened packages are sealed against deep reflection, even for public members of non-exported types and for non-public members generally. When you call setAccessible(true) on a member whose declaring package is not 'open' to your module, you get InaccessibleObjectException — an unchecked exception — instead of silent success. The fix is to grant deep-reflective access: in the target module's module-info add `opens com.foo.pkg;` (or `opens com.foo.pkg to your.module;` for a qualified open), or for code you don't control pass `--add-opens java.base/java.util=ALL-UNNAMED` at launch. JDK internals (java.base) are sealed this way, which is why old libraries that reflected into them broke. On the plain classpath (no module-info), your code is in the unnamed module and most non-JDK packages are still openable, so this mainly bites against JDK internals or explicitly modular libraries.

code

java · 13 lines
java
// Reflecting into a JDK internal triggers the module guard:
Field f = java.util.ArrayList.class.getDeclaredField("elementData");
// f.setAccessible(true);
//   -> InaccessibleObjectException:
//      module java.base does not "opens java.util" to unnamed module

// Fix at launch:
//   java --add-opens java.base/java.util=ALL-UNNAMED -cp app.jar App

// Or, in YOUR module's module-info.java, to expose your own internals:
//   module com.foo {
//     opens com.foo.internal to com.fasterxml.jackson.databind;
//   }

go deeper

for a junior

Recognizes the exception name and that Java 9 modules made some reflection fail; knows an --add-opens flag is involved.

for a middle

Explains strong encapsulation, the opens vs exports distinction, and can fix it with a module-info opens or an --add-opens launch flag.

for a senior

Knows classpath/unnamed-module nuances, qualified opens for least privilege, manifest Add-Opens, and why java.base breaks; uses trySetAccessible to degrade gracefully.

for a principal

Reasons about library packaging: shipping modular jars with minimal qualified opens vs forcing consumers to add launch flags; migration strategy and the long-term tightening of JDK strong encapsulation.

**Background — the module system (JPMS):** Java 9 added *modules*. A module is a named group of packages described by a `module-info.java` file. By default a module *hides* its packages: other code can only use a package if the module **`exports`** it (for compile/normal access) or **`opens`** it (for deep reflection). This is called **strong encapsulation**. The whole JDK was modularized — the core module is **`java.base`** (containing `java.lang`, `java.util`, etc.), and its internals are *not* open. **What 'deep reflection' means:** ordinary reflection on *public* members of *exported* types is fine. 'Deep' reflection means using `setAccessible(true)` to break encapsulation — reaching `private` members, or reaching into types in packages that were not made available. JPMS gates exactly this. **The exception:** `java.lang.reflect.InaccessibleObjectException` is a `RuntimeException` (unchecked, added in Java 9) thrown by `setAccessible(true)` / `trySetAccessible()`-when-forced when the **module that contains the member has not opened the member's package to the module of the caller**. Conceptually, the check is: *is `targetPackage` open to `callerModule`?* If not → throw. This replaced Java 8's behavior where setAccessible almost always worked. **Concretely you hit it when:** - You reflect into a JDK internal, e.g. `java.util.ArrayList`'s private `elementData`, or `sun.*`/`jdk.internal.*` types — `java.base` does not open these. - You reflect into a *modular* third-party library that didn't `opens` its package. **How to fix it (three levels):** 1. **You own the target module:** add an `opens` directive to its `module-info.java`: - `opens com.foo.internal;` — opens to *everyone* for deep reflection. - `opens com.foo.internal to com.bar.framework;` — *qualified open*, only to that one module (least privilege). 2. **You do NOT own the target (e.g. the JDK):** open it at launch with a command-line flag: - `--add-opens java.base/java.util=ALL-UNNAMED` opens `java.util` (in `java.base`) to all unnamed-module code (classpath code). - The syntax is `--add-opens <source-module>/<package>=<target-module>`; `ALL-UNNAMED` targets classpath code. You can also set it via the `Add-Opens` manifest attribute in an executable jar. 3. **Probe instead of crashing:** call **`trySetAccessible()`** (returns `false` rather than throwing) or **`canAccess(obj)`** and fall back to a non-reflective path. **Classpath vs module path:** if your application has *no* `module-info.java`, your classes run in the **unnamed module**, which can read all modules and to which most ordinary modules implicitly open. So everyday `setAccessible` on your *own* classpath classes still works; the exception mainly appears against **JDK internals** (always sealed) or **explicitly modular libraries**. This is why upgrading from Java 8 to 9+ broke tools like older Hibernate/Lombok/mocking libs that poked at `java.*` internals — the fix was usually an `--add-opens` line. **Direction matters:** `opens`/`--add-opens` is granted *by the module that owns the package* to the *module that wants in*. You cannot grant yourself access to someone else's sealed module from your own module-info; that is the point of strong encapsulation. The launch flag exists precisely as the escape hatch the application assembler (not the library) controls.

  • What is the difference between the 'exports' and 'opens' module directives?
    'exports' makes a package's PUBLIC types usable for normal compile-time/runtime access by other modules. 'opens' grants DEEP reflective access (setAccessible into private members) at runtime only — it does not allow compile-time use. Frameworks that inject into private fields need 'opens', not 'exports'.
  • Why does setAccessible still work on your own classpath classes but not on java.util internals?
    Classpath code runs in the unnamed module, and named modules implicitly open to the unnamed module for many packages; your own classes have no module restricting them. But java.base never opens its internal packages to anyone, so reflecting into java.util/jdk.internal fails without an explicit --add-opens.

exports is publishing your phone number (people can call you normally). opens is giving someone a key to walk into your house (deep reflection). The JDK changed all its locks in Java 9 and stopped handing out house keys, so old code that walked in freely now needs you to mint a key with --add-opens.

saying these in an interview costs you the question

  • Confusing 'exports' (normal access) with 'opens' (deep reflection) — using exports to fix an InaccessibleObjectException won't help.
  • Thinking you can grant yourself access to another module from your own module-info — only the owning module's opens or a launch flag can.
  • Believing the exception is checked — it is an unchecked RuntimeException.
  • Claiming --add-opens is needed for all reflection — only for deep reflection into sealed/unopened packages.

context