skip to content

What happened to internal JDK APIs like sun.* and com.sun.* in the module system, and how do you cope when your code depends on them?

level: seniorimportance: must knowfreq 58%

answer

  1. sun.*/com.sun.* now encapsulated, not exported
  2. jdeps --jdkinternals finds them + suggests replacements
  3. Default-deny reflection since Java 16/17
  4. --add-exports = read public types; --add-opens = deep reflection
  5. jdk.unsupported is a temporary runway for Unsafe

basics

~20 s

In Java 9+, internal JDK packages (like sun.misc.Unsafe) are encapsulated by their modules and no longer exported, so code that used them fails to compile or warns/breaks at runtime. The right fix is to switch to a supported public API; as a temporary workaround you can use --add-exports or --add-opens.

solid answer

~40 s

Before Java 9 the JDK was one big classpath, so internal packages such as sun.misc, sun.security.*, and com.sun.* were reachable even though unsupported. With JPMS the JDK is split into modules (java.base, etc.) that strongly encapsulate these packages — they are not exported, so referencing them gives compile errors or, for reflection, illegal-access warnings then hard failures (default-deny since Java 16/17). Coping strategy, in order: first migrate to a supported replacement (e.g. Unsafe operations move to VarHandle, MethodHandles, java.util.concurrent.atomic, or the Foreign Function & Memory API; base64 in java.util.Base64). If no replacement exists yet, use the escape hatches: --add-exports module/package=ALL-UNNAMED to read non-public API at compile/run, and --add-opens for deep reflection (setAccessible). jdeps --jdkinternals reports which internal APIs you use and suggests replacements. Treat the flags as a bridge, not a destination.

go deeper

for a junior

Knows that sun./com.sun. are internal/unsupported and that newer Java may block them, and would look for a public replacement.

for a middle

Uses jdeps --jdkinternals to inventory usages and replaces common ones (Base64, etc.); knows the --add-exports/--add-opens flags exist.

for a senior

Clearly distinguishes --add-exports vs --add-opens, knows the default-deny timeline (16/17) and the jdk.unsupported runway, and migrates Unsafe to VarHandle/FFM.

for a principal

Sets org-wide policy for eliminating internal-API dependence, audits the dependency graph (including transitive libs), plans staged upgrades across LTS versions, and avoids baking escape-hatch flags into long-lived runtime config.

## Why internal APIs existed and were reachable Before Java 9 the entire JDK lived on the classpath as `rt.jar`. There was no enforced boundary between **public API** (documented, supported, e.g. `java.util.*`) and **internal implementation** packages like `sun.misc`, `sun.security.*`, `sun.nio.*`, and `com.sun.*`. Because nothing stopped you, popular libraries reached into internals — most famously `sun.misc.Unsafe` for low-level memory and concurrency tricks. ## What JPMS changed The Java Platform Module System (Java 9) **modularized the JDK itself** into modules like `java.base`, `java.sql`, `java.xml`. A core feature is **strong encapsulation**: a module's package is only accessible to others if it is `exports`-ed (or `opens`-ed for reflection). The JDK modules deliberately **do not export** their internal packages. So: - **Compile time:** `javac` refuses to resolve `sun.misc.Unsafe` unless you explicitly add an export. - **Run time / reflection:** code that used reflection to poke at internals triggered *illegal reflective access* warnings in Java 9–15, and since **Java 16 (JEP 396) / Java 17** the default is **deny** — such access throws `InaccessibleObjectException` unless explicitly opened. A few widely-used internals (notably critical bits of `sun.misc.Unsafe`) were temporarily exported via the `jdk.unsupported` module to avoid breaking the ecosystem overnight, but that is a deprecation runway, not a promise. ## How to diagnose Run **`jdeps --jdkinternals <your.jar>`** (or `jdeps -jdkinternals`). It scans bytecode for references to JDK-internal packages and prints each use plus a **suggested supported replacement**. ## How to cope, in priority order 1. **Migrate to a supported API.** Most internal uses now have public equivalents: - `sun.misc.BASE64Encoder` → `java.util.Base64` - much of `sun.misc.Unsafe` → `java.lang.invoke.VarHandle`, `MethodHandles`, `java.util.concurrent.atomic`, and the **Foreign Function & Memory API** (`java.lang.foreign`). - `com.sun.*` HTTP server / JAXB / activation → standalone Jakarta/third-party artifacts. 2. **Escape hatches (transition only):** - **`--add-exports <module>/<package>=<target>`** — lets you *read* a non-exported package (compile and run). Target `ALL-UNNAMED` covers classpath code. - **`--add-opens <module>/<package>=<target>`** — grants **deep reflection** (`setAccessible(true)`) into a package; needed because export alone does not permit reflecting on non-public members. - These can also go in the jar manifest (`Add-Opens`) or, for launched apps, in `JDK_JAVA_OPTIONS`. 3. **`--illegal-access=permit`** existed in 9–15 to soften the runtime behavior, but it was **removed in Java 17**; do not rely on it. ## --add-exports vs --add-opens (the common confusion) - `--add-exports` = compile/link visibility of a package's **public** types. - `--add-opens` = runtime **deep reflective** access (private fields/methods). Many frameworks (serializers, DI, mocking) need `--add-opens` even into *your own* or standard modules. ## Key takeaways Internal `sun.*`/`com.sun.*` APIs were never supported; JPMS finally enforced that. The durable path is replacing them with public APIs surfaced by `jdeps --jdkinternals`; the `--add-exports`/`--add-opens` flags are bridges to keep you running while you migrate.

  • What is the difference between --add-exports and --add-opens?
    --add-exports makes a non-exported package's public types accessible at compile and run time. --add-opens additionally grants deep reflective access (setAccessible on private members), which frameworks like serialization and DI containers require. Exporting alone does not permit reflection on non-public members.
  • How would you find every internal-JDK dependency in a legacy jar before upgrading?
    Run jdeps --jdkinternals (or -jdkinternals) on the jar. It scans bytecode for references to JDK-internal packages, lists each occurrence, and prints a suggested supported replacement for many of them.

saying these in an interview costs you the question

  • Calling sun.misc.Unsafe a supported/public API
  • Thinking --add-opens is just an alias for --add-exports
  • Relying on --illegal-access=permit (removed in Java 17)
  • Assuming the encapsulation only causes warnings — since 16/17 it hard-fails by default
  • Treating the flags as a permanent solution instead of migrating

context