Part 11 of 14

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:

JEPChangeImpact
403Strongly Encapsulate JDK InternalsHIGH — breaks libraries using internal APIs
407Remove RMI ActivationLOW — niche feature, deprecated since Java 15
411Deprecate Security ManagerMEDIUM — some applications use SecurityManager
306Restore Always-Strict Floating-PointLOW — 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:

LibraryIssueFixed version
Mockito < 5Uses reflection on JDK internalsMockito 5.x
ByteBuddy < 1.14Class instrumentation on internal classesByteBuddy 1.14+
Kryo < 5Reflection-based serializationKryo 5.x
cglibGenerates subclasses via internal APIsReplaced by ByteBuddy in Spring
Spring < 6Various internal APIsSpring 6.x (requires Java 17)
Hibernate < 6Reflection on Java typesHibernate 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:

  • Activatable
  • ActivationDesc
  • ActivationGroup
  • ActivationGroup_Stub
  • ActivationGroupDesc
  • ActivationGroupID
  • ActivationID
  • ActivationInstantiator
  • ActivationMonitor
  • ActivationSystem
  • Activator

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:

  1. OS-level sandboxing: seccomp-bpf on Linux, pledge/unveil on OpenBSD, App Sandbox on macOS
  2. Container isolation: Run untrusted code in a container with resource limits
  3. Java Modules: Use the module system’s encapsulation to limit what untrusted code can access
  4. 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):

  1. Run your existing test suite under Java 17 — most tests will pass unchanged
  2. If you see failures in floating-point comparison tests, the test may have been relying on the non-strict behavior
  3. 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.