🔴 The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOG// DOC-DERIVED — wording stable on JDK 16+ (JEP 396/403); hex id varies per run: Exception in thread "main" java.lang.IllegalAccessError: class com.devinhyderabad.tools.Profiler cannot access class sun.nio.ch.DirectBuffer (in module java.base) because module java.base does not export sun.nio.ch to unnamed module @1a6c5a8e at com.devinhyderabad.tools.Profiler.sample(Profiler.java:22) at com.devinhyderabad.app.Main.main(Main.java:6)

⚡ Quick Fix Works 80% of the time

Replace the internal API with its supported counterpart; if migration must wait, add a targeted --add-exports bridge for exactly the package and module involved.

# bridge, scoped tight (production-safe interim measure) java --add-exports=java.base/sun.nio.ch=ALL-UNNAMED -jar app.jar # in Dockerfile / systemd unit: ENV JAVA_TOOL_OPTIONS="--add-exports=java.base/sun.nio.ch=ALL-UNNAMED"

🧠 Why this Happens

Tap to expand the deep technical explanation

Since Java 9 every type belongs to a module, and cross-module access requires the exporting module to open that package to the reader. Classpath code forms the unnamed module, which may only read packages another module exports publicly. sun.nio.ch was always internal; before Java 16 illegal access merely warned. JEP 396 made strong encapsulation the default in 16 and JEP 403 removed the last override switch in 17 — so at first linkage to DirectBuffer the access check fails and the JVM throws IllegalAccessError, not a warning.

The HITEC City Parking Spot Analogy:

For years a side door in the office was unlocked with a polite sign asking staff not to use it. Renovation welded it shut — anyone who kept using the shortcut now finds a wall.

🔁 How to Reproduce Confirm this is your error

Reference any java.base internal package (sun.misc.Unsafe, sun.nio.ch.DirectBuffer) from plain classpath code on JDK 17+ and invoke it. Compile-time javac already refuses; reflective or bytecode-driven access fails at runtime with this wording. DOC-DERIVED.

🛠️ Solutions (5 Ways to Fix)

Solution 1✓ Most common cause

Migrate to the supported replacement API

👉 Use this as the durable fix whenever the library or your own code owns the offending call sites.

Every popular internal has an official successor: sun.misc.Unsafe field access becomes VarHandle via MethodHandles.Lookup; sun.nio.ch.DirectBuffer becomes the Foreign Function & Memory API (finalized in 22, preview earlier); CORBA-era internals have standalone artifacts. Migrating removes the dependency on undocumented behavior.

// sun.misc.Unsafe.objectFieldOffset -> VarHandle VarHandle vh = MethodHandles.lookup() .findVarHandle(Order.class, "total", BigDecimal.class); vh.set(order, new BigDecimal("99.90"));
Solution 2

Bridge with a narrowly scoped --add-exports

👉 Use this while a third-party library you cannot patch still needs the internal package.

One flag pair per package, naming the exact reader. Avoid ALL-UNNAMED for everything; enumerate the modules you truly need so future JDK hardening does not silently break other paths. Document every bridge with the issue that will retire it.

# example: a metrics lib reading buffer counters java --add-exports=java.base/sun.nio.ch=ALL-UNNAMED \ --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED \ -jar app.jar
Solution 3

Upgrade the offending library to a JDK-17-ready release

👉 Use this when the stack frame above yours belongs to a vendor jar, not your code.

Most active projects shipped strong-encapsulation-compatible builds years ago; check changelogs for JPMS notes. Upgrading deletes your --add-exports debt instead of accumulating it.

mvn -q dependency:tree -Dincludes=net.bytebuddy,org.objenesis,com.zaxxer # then bump each to the release certified for your JDK line
Solution 4

Declare Add-Exports in the executable jar manifest

👉 Use this for java -jar deployments where command-line flags tend to get lost between environments.

The manifest attribute travels with the artifact, keeping runtime behavior identical everywhere the jar runs without depending on operators remembering flags.

Manifest-Version: 1.0 Main-Class: com.devinhyderabad.app.Main Add-Exports: java.base/sun.nio.ch java.base/sun.misc
Solution 5

Open every package to everything at startup

👉 DEV ONLY — never ship this; see below.

DEV ONLY. Blanket --add-opens/--add-exports for all modules silences every encapsulation error at once but destroys the boundary the platform enforces for security and stability, and hides the dozen individual migrations you actually owe. Fine for one local reproducibility test; forbidden in images and units.

# DEV ONLY - nukes encapsulation globally java --add-opens java.base/java.lang=ALL-UNNAMED \ --add-opens java.base/java.util=ALL-UNNAMED \ --add-exports java.base/sun.nio.ch=ALL-UNNAMED \ -jar app.jar

📋 Version Notes

Java 8

No modules; sun.* internals accessible with mere deprecation warnings.

Java 11

Illegal reflective access warns by default (JEP 261 relaxed mode).

Java 17

JEP 403: --illegal-access removed; JDK internals deny access outright.

Java 21

Same posture; FFM API preview path for direct buffers matures here.

🛡️ How to Prevent This Next Time

Compile with --release matching production and jdeps your fat jars for internal-API usage on every dependency bump; track deprecation warnings (javac -Xlint:jdkinternals) as work items, not noise.