Strong Encapsulation of JDK Internals in Java 16 and 17
Introduction
For years, many libraries and applications reached into the JDK's internal classes -
The error in action
The program below tries to read the private
OUTPUT of the above code
Explanation of the code
OUTPUT of the above command
The old global switch no longer exists from Java 17 -
OUTPUT of the above command
The JVM prints a warning that the option is ignored, and the access still fails.
What is still accessible: sun.misc.Unsafe
A few critical internal APIs, for which there were no public replacements, were deliberately left accessible in the
OUTPUT of the above code
These too are being replaced by supported APIs -
sun.* and com.sun.* packages, and private fields of classes like String - using reflection and setAccessible(true). The module systemClick to read: module system of Java 9 made these internals non-exported, but kept a loophole open so that existing code would not break: reflective access was allowed with a warning. Java 16Click to read tutorial: Overview of New Features in Java 16 (JEP 396) closed the loophole by default, and Java 17Click to read tutorial: Overview of New Features in Java 17 (JEP 403) removed the option to reopen it globally. This tutorial explains the strong encapsulation of JDK internals: what changed in each release, the errors you get, which internals are still accessible, and how to fix or work around the errors.
Why encapsulate the internals?
- Security: internals such as the private fields of
Stringor the JDK's class-loading machinery could be changed by any code, undermining guarantees the platform relies on. - Maintainability: code depending on internals breaks whenever the JDK changes them - which held back JDK development and made upgrades painful. Many problems when moving from Java 8 to 11 came from libraries using internals.
- Integrity: with the internals encapsulated, the JDK can optimize and change its implementation freely, as long as the public APIs stay the same.
- Java 9 to 15: code on the class path could still use deep reflection on JDK internals which existed in Java 8, with a single warning: "WARNING: An illegal reflective access operation has occurred". The option
--illegal-accesscontrolled this, withpermitas the default. - Java 16: the default became
--illegal-access=deny: such access now fails with an exception.--illegal-access=permitcould still restore the old behaviour. - Java 17: the
--illegal-accessoption was removed - the JVM ignores it with a warning. The only way to open an internal package is now explicitly, one package at a time, with--add-opens.
value field inside a String, a classic example of deep reflection into the JDK -
Reflective access to a JDK internal field
//DeepReflection.java
import java.lang.reflect.Field;
import java.lang.reflect.InaccessibleObjectException;
public class DeepReflection {
public static void main(String[] args) throws Exception {
Field value = String.class.getDeclaredField("value");
try {
value.setAccessible(true);
byte[] bytes = (byte[]) value.get("Java");
System.out.println("Read String's internal bytes: " + bytes.length);
} catch (InaccessibleObjectException e) {
System.out.println("InaccessibleObjectException:\n " + e.getMessage().replaceAll("@[0-9a-f]+", "@..."));
}
}
}
InaccessibleObjectException: Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @...
getDeclaredField()still works - you can see that the field exists - butsetAccessible(true)throws anInaccessibleObjectException(aRuntimeExceptionadded in Java 9).- The message explains why: the
java.basemodule does not open the packagejava.langto our code, which lives in the unnamed module because it is on the class path. (The module's identity hash is replaced by "..." in the output.) - Ordinary use of public APIs is unaffected - only deep reflection into non-public members of JDK internals, and direct use of non-exported packages, is blocked.
--add-opens module/package=target-module, where ALL-UNNAMED means all code on the class path -
java --add-opens java.base/java.lang=ALL-UNNAMED DeepReflection
Read String's internal bytes: 4
java --illegal-access=permit DeepReflection
Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option --illegal-access=permit; support was removed in 17.0 InaccessibleObjectException: Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @...
--add-opens (and --add-exports, for compile-time and run-time use of non-exported packages) can also be put in the Add-Opens attribute of an executable JAR's manifest, or in the JDK_JAVA_OPTIONS environment variable.
jdk.unsupported module - most importantly sun.misc.Unsafe, used by many high-performance libraries, and sun.reflect.ReflectionFactory, used by serialization libraries -
sun.misc.Unsafe is still accessible
//UnsafeStillThere.java
import java.lang.reflect.Field;
public class UnsafeStillThere {
public static void main(String[] args) throws Exception {
Field f = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true); // allowed: jdk.unsupported opens sun.misc
Object unsafe = f.get(null);
System.out.println("Got " + unsafe.getClass().getName() + " from module " + unsafe.getClass().getModule().getName());
}
}
Got sun.misc.Unsafe from module jdk.unsupported
VarHandle (Java 9) and the Foreign Function & Memory APIClick to read: Foreign Function & Memory API (Java 22) cover most uses of Unsafe, and its memory-access methods were deprecated for removal in Java 23, with warnings on use from Java 24.
How to migrate
- Find the problems: run
jdeps --jdk-internalson your application and its libraries, which lists uses of JDK internals and suggests replacements, and run your tests on the new Java version. - Upgrade libraries: most errors come from old versions of libraries (bytecode generators, serializers, mocking frameworks, dependency injection). Their current versions use supported APIs.
- Replace internal APIs in your own code with public ones - for example
java.util.Base64instead ofsun.misc.BASE64Encoder, orMethodHandlesandVarHandleinstead of reflection hacks. - Use --add-opens only as a temporary measure, for the specific packages that still need it, and track each one so it can be removed.
--illegal-access option. We saw the InaccessibleObjectException this causes, opened a single package with --add-opens, and saw that sun.misc.Unsafe remains accessible through the jdk.unsupported module. Lastly, we went through the steps to migrate code which uses JDK internals. Next, read the overview of new features in Java 17Click to read tutorial: Overview of New Features in Java 17.Continue Reading Java 12 to 17 Tutorials
Overview of New Features in Java 16Click to read tutorial: Overview of New Features in Java 16 Java 16 Records Tutorial with ExamplesClick to read tutorial: Java 16 Records Tutorial with Examples Java 16 Pattern Matching for instanceofClick to read tutorial: Java 16 Pattern Matching for instanceof Java 16 API Additions: Stream.toList, mapMulti and MoreClick to read tutorial: Java 16 API Additions: Stream.toList, mapMulti and More jpackage Tutorial: Packaging Java Apps (Java 16)Click to read tutorial: jpackage Tutorial: Packaging Java Apps (Java 16) Overview of New Features in Java 17Click to read tutorial: Overview of New Features in Java 17 Overview of New Features in Java 12Click to read tutorial: Overview of New Features in Java 12 Overview of New Features in Java 13Click to read tutorial: Overview of New Features in Java 13 Overview of New Features in Java 14Click to read tutorial: Overview of New Features in Java 14
Overview of New Features in Java 16Click to read tutorial: Overview of New Features in Java 16 Java 16 Records Tutorial with ExamplesClick to read tutorial: Java 16 Records Tutorial with Examples Java 16 Pattern Matching for instanceofClick to read tutorial: Java 16 Pattern Matching for instanceof Java 16 API Additions: Stream.toList, mapMulti and MoreClick to read tutorial: Java 16 API Additions: Stream.toList, mapMulti and More jpackage Tutorial: Packaging Java Apps (Java 16)Click to read tutorial: jpackage Tutorial: Packaging Java Apps (Java 16) Overview of New Features in Java 17Click to read tutorial: Overview of New Features in Java 17 Overview of New Features in Java 12Click to read tutorial: Overview of New Features in Java 12 Overview of New Features in Java 13Click to read tutorial: Overview of New Features in Java 13 Overview of New Features in Java 14Click to read tutorial: Overview of New Features in Java 14