JDK Encapsulation and Removed APIs (JEP 403, 306, 407, 411): What It Means for Your Code
Overview
Java 17 includes several platform changes that can break existing code during migration. This article covers four JEPs that affect compatibility:
| JEP | Change | Impact |
|---|---|---|
| 403 | Strongly Encapsulate JDK Internals | HIGH — breaks libraries using internal APIs |
| 407 | Remove RMI Activation | LOW — niche feature, deprecated since Java 15 |
| 411 | Deprecate Security Manager | MEDIUM — some applications use SecurityManager |
| 306 | Restore Always-Strict Floating-Point | LOW — affects numeric edge cases |
JEP 403: Strongly Encapsulate JDK Internals
What Changed
In Java 8 and earlier, code could access any JDK internal API via reflection — setAccessible(true) bypassed access controls. This enabled libraries to use private JDK fields and methods for legitimate purposes (Serialization frameworks, ORM tools, aspect libraries, test utilities).
Java 9 warned about such access. Java 11–16 allowed it via the --illegal-access flag. Java 17 removes --illegal-access entirely. All internal JDK APIs are now strongly encapsulated: access via reflection throws InaccessibleObjectException.
# Java 11–16: this warned but worked
java --illegal-access=permit MyApp
# Java 17: this flag is gone
java --illegal-access=permit MyApp
# Error: java.lang.IllegalArgumentException: --illegal-access not recognized
The Symptom
java.lang.reflect.InaccessibleObjectException:
Unable to make field private final byte[] java.lang.String.value accessible:
module java.base does not "opens java.lang" to unnamed module @5e2de80c
This happens when code (typically a library) calls setAccessible(true) on a private JDK field.
Immediate Fix: --add-opens
The short-term fix is to explicitly open the affected JDK package to your module (or to all unnamed modules):
java --add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/java.lang.reflect=ALL-UNNAMED \
MyApp
In Maven Surefire (for tests):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
In Gradle:
tasks.withType<Test> {
jvmArgs(
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
"--add-opens", "java.base/java.util=ALL-UNNAMED"
)
}
Root Fix: Upgrade the Library
--add-opens is technical debt. Each flag is a signal that a library is using JDK internals it shouldn’t. The root fix is to upgrade the library to a version that uses public APIs.
Common offenders and their fixed versions:
| Library | Issue | Fixed version |
|---|---|---|
| Mockito < 5 | Uses reflection on JDK internals | Mockito 5.x |
| ByteBuddy < 1.14 | Class instrumentation on internal classes | ByteBuddy 1.14+ |
| Kryo < 5 | Reflection-based serialization | Kryo 5.x |
| cglib | Generates subclasses via internal APIs | Replaced by ByteBuddy in Spring |
| Spring < 6 | Various internal APIs | Spring 6.x (requires Java 17) |
| Hibernate < 6 | Reflection on Java types | Hibernate 6.x |
Track each --add-opens you add. Eliminate them by upgrading the corresponding library.
Which Packages Are Affected?
Common packages that libraries access and that now require --add-opens:
java.base/java.lang — String internals, Module access
java.base/java.util — Collection internals
java.base/java.lang.reflect — Reflection internals
java.base/java.io — Serialization internals
java.base/sun.security.x509 — Certificate internals
java.base/sun.nio.ch — NIO channel internals
java.desktop/java.awt — AWT internals (GUI apps)
jdk.compiler/com.sun.tools.javac — Compiler API internals (annotation processors)
Use jdeps to identify what your code and dependencies access:
jdeps --jdk-internals --multi-release 17 myapp.jar
--add-exports
Some libraries access JDK packages that are not exported at all (not just not opened to reflection). These require --add-exports:
# Make jdk.compiler's internal package visible
java --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED MyApp
The difference:
--add-opens: allows reflective access to internal package members--add-exports: makes the package’s types visible at compile/link time
JEP 407: Remove RMI Activation
What Is RMI Activation?
Java RMI (Remote Method Invocation) allows objects in one JVM to call methods in another. RMI Activation was an add-on mechanism that allowed dormant remote objects to be activated on demand — like a server that starts only when called.
The java.rmi.activation package was deprecated in Java 15 (JEP 385) and removed entirely in Java 17.
What Was Removed
The entire java.rmi.activation package, including:
ActivatableActivationDescActivationGroupActivationGroup_StubActivationGroupDescActivationGroupIDActivationIDActivationInstantiatorActivationMonitorActivationSystemActivator
The rmid tool (Activation System Daemon) was also removed.
Impact Assessment
Check if your code uses these APIs:
jdeprscan --release 17 --for-removal myapp.jar
If you see java.rmi.activation in the output, you are affected.
Migration
RMI Activation was used to start server-side objects on demand. Modern replacements:
- gRPC (protocol buffers, bidirectional streaming, service discovery)
- REST (HTTP-based remote calls, widely supported)
- Spring Remoting (already deprecated Spring’s RMI support in favor of REST)
- Apache Thrift, Apache Dubbo (full RPC frameworks)
The vast majority of codebases do not use RMI Activation. If yours does, the migration will be significant but the replacement technologies are mature.
JEP 411: Deprecate the Security Manager for Removal
What Is the Security Manager?
java.lang.SecurityManager (and its companion java.security.Policy) was the original Java sandbox mechanism. It allowed:
- Restricting which files an application could read or write
- Controlling network connections
- Limiting thread creation
- Restricting access to system properties
It was used primarily for applets (now gone) and Java Web Start (also discontinued) to sandbox untrusted code running in the browser.
What Changed in Java 17
The Security Manager is deprecated for removal (not yet removed) in Java 17. Running with a Security Manager now prints:
WARNING: A terminally deprecated method in java.lang.SecurityManager has been called
WARNING: Please consider reporting this to the maintainers of com.example.MyApp
WARNING: SecurityManager::setSecurityManager will be removed in a future release
The Security Manager is expected to be removed in Java 24 (based on the deprecation trajectory).
Impact Assessment
# Check if your code installs a SecurityManager
grep -r "SecurityManager\|setSecurityManager\|checkPermission" src/
Also check: Maven Surefire’s forkCount configuration and test framework isolation mechanisms sometimes use SecurityManager.
Migration
For applications that use SecurityManager to sandbox plugins or untrusted code:
- OS-level sandboxing:
seccomp-bpfon Linux,pledge/unveilon OpenBSD, App Sandbox on macOS - Container isolation: Run untrusted code in a container with resource limits
- Java Modules: Use the module system’s encapsulation to limit what untrusted code can access
- Separate process: Run untrusted plugins in a child process with restricted OS permissions
For applications that check permissions in checkPermission:
// Old pattern — will fail when SecurityManager is removed
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkRead(path);
}
// Modern — use explicit authorization instead
if (!userHasReadPermission(currentUser, path)) {
throw new SecurityException("Access denied");
}
JEP 306: Restore Always-Strict Floating-Point Semantics
Background
In Java 1.0, all floating-point operations used the IEEE 754 standard strictly (strictfp). In Java 1.2, strictfp was made opt-in — non-strict mode allowed the JVM to use extended-precision intermediates (80-bit registers on x87 FPUs), which gave different results on different hardware.
By 2021, x87 instructions were superseded by SSE2 and AVX, which always use 64-bit doubles. The extended-precision exception was no longer needed.
What Changed
Java 17 makes strictfp the default for all floating-point operations — the strictfp modifier is now redundant (retained for source compatibility but has no effect).
Impact
- No API changes — this is a semantic change, not an API change
- Numerically sensitive code may see different results in edge cases (values near overflow, underflow, or rounding boundaries)
- Most applications will see no change at all — typical business logic, web services, and data processing code is unaffected
Action Required
If your application does numerical computation (financial calculations, scientific simulations, signal processing):
- Run your existing test suite under Java 17 — most tests will pass unchanged
- If you see failures in floating-point comparison tests, the test may have been relying on the non-strict behavior
- Adjust expected values to match IEEE 754 strict semantics, which are now universal
// Before Java 17: might get extended-precision result on some hardware
double result = a * b + c;
// Java 17: always strict IEEE 754 — same result on all hardware
double result = a * b + c;
// The strictfp modifier is now a no-op
strictfp double result = a * b + c; // same as above
Summary Migration Checklist for JEP 403, 407, 411, 306
JEP 403 — Strong Encapsulation
[ ] Run: jdeps --jdk-internals myapp.jar
[ ] Add --add-opens for each InaccessibleObjectException (temporary)
[ ] Upgrade libraries to Java 17-compatible versions
[ ] Remove --add-opens flags once libraries are upgraded
[ ] Audit remaining --add-opens as ongoing technical debt
JEP 407 — Remove RMI Activation
[ ] Run: jdeprscan --release 17 --for-removal myapp.jar
[ ] Identify any java.rmi.activation usage
[ ] Plan migration to gRPC or REST if affected
JEP 411 — Deprecate Security Manager
[ ] Grep for SecurityManager, setSecurityManager, checkPermission
[ ] If found: plan migration to container-based or OS-based sandboxing
[ ] Monitor: Security Manager removal expected in Java 24+
JEP 306 — Strict Floating-Point
[ ] Run full test suite on Java 17
[ ] Fix any floating-point comparison failures by updating expected values
[ ] No action needed if tests pass
What’s Next
Article 12: Foreign Function & Memory API (JEP 412) covers Java 17’s first incubator iteration of Project Panama — the API that will eventually replace JNI for native code interop.