Encapsulation for modular JDKs

The new reality is that when Java code is compiled with a modular JDK (all JDKs from version 9 onwards are modular), only methods on exported packages are accessible. It is no longer the case that a public method on a public class is automatically accessible to all code everywhere.

In other words, the platform can finally enforce the long-standing convention that in the JDK, a package that starts java or javax is a public API and everything else is internal-only.

In Java 8 and before, the convention is just that: a convention. Until the arrival of modules, there was no VM or class loading mechanism that enforced that, as I’ve shown.

The basic semantics of the Java module system close off the ability for libraries and applications to link directly to the JDK internals. This means that all applications that have upgraded from Java 8 are already safer—because they are guaranteed only to directly access the JDK via its public API.

However, when it has suited them, programmers have also coupled to the internals using reflection, and this aspect of encapsulation is more complex.

The treatment of reflection is at the heart of what the high-level log message means. To explore what the message means in detail, I’ll explain how modules impact reflection.

Java has supported reflection since almost the very beginning of its existence. Reflection allows you to access and work with types indirectly and, at runtime, in a way that does not require compile-time knowledge of those types. This flexibility is at the heart of many of the most popular Java frameworks and libraries.

As you have already seen, modules add a new concept to Java’s access control model—the idea of exporting a package—which declares a package to be part of the module’s API.

Reflection, however, is not direct linkage. The Java Reflection API has always had a huge encapsulation hole in the form of a method called setAccessible().

The setAccessible() method can be called on an object that represents, for example, a method on an unknown (at compile time) class. The method tells the JVM that it should skip access-control checks when trying to access or call the underlying (unknown at compile time) method. This method is very useful for framework designers, but it represents a major leakage of encapsulation safety. Modules needed to address this case as well.

Reflective access to modularized code

In general, modules can declare their reflective access policy as part of their module-info.class, which is done with the opens keyword. The intent is that, by default, only exported packages can be accessed reflectively, but the designers of the modules system realized that sometimes developers want to give reflective (but not direct) access to certain packages.

Thus, the opens keyword allows a module to declare that a certain set of packages is available for reflective access, even if the packages are not part of the module’s public API. Developers can also specify fine-grained access by using the syntax opens … to … allow a named set of packages to be opened reflectively to specific modules but not more generally.

I’ll make this concept more concrete with an example that uses an internal utility method to parse the bytecode name of a method. For the sake of this example, suppose someone is building a library and want it to build on all Java versions from 8 through 15 without the compiler errors shown earlier. To do that, he would refer to the class sun.invoke.util.BytecodeName indirectly via reflection.

What’s changing in Java 17?

In Java 16, it is still possible to restore the situation that existed previously by using the –illegal-access command-line switch to allow general reflective access to the JDK internals. However, in Java 17, the situation changes again: This release removes that command-line switch. This topic is covered in detail by JEP 403: Strongly encapsulate JDK internals.

Java 16 and Java 17 are providing a foundation of enhanced encapsulation that will provide a safer and simpler future for both application developers and JDK internals programmers alike.

Tags: , , , , , , , , , , , , , ,