Search code examples
javajava-modulejava-platform-module-system

Bypassing Java modularity via reflection


Is the Java Module System supposed to prevent modules accessing other modules via reflection, without declaring proper module dependencies?

For example, when compiling this hello world Java 11 class, which calls a class from another module, as expected it won't compile, because dependency on java.xml is missing:

module m1 {}

package p1;
public class C1 {
    public static void main(String[] args) throws Exception {
        System.out.println(javax.xml.XMLConstants.XML_NS_URI);
    }
}

After adding module dependency to java.xml it compiles and runs as expected.

However, this class:

module m1 {}

package p1;
public class C1 {
    public static void main(String[] args) throws Exception {
       System.out.println(Class.forName("javax.xml.XMLConstants").getField("XML_NS_URI").get(null));
    }
}

runs and prints the result, without any need to declare a module dependency to java.xml:

java -version
openjdk version "11.0.2" 2019-01-15

java -p bin -m m1/p1.C1
http://www.w3.org/XML/1998/namespace

So effectively we are able to bypass Java modularity.

This works from any module to any other resolved module.

How is that possible? Should the module system prevent such scenario?


Solution

  • There are two types of readability in the Java module system: static readability and reflective readability. In the first versions of the module system (during its development before it was released in Java 9), these two types were the same. So, your second example indeed would fail with an error because there is no requires clause from m1 to java.xml. Later this policy was revised and the rules for reflective readability were relaxed because such strict policy hadn't played well with many frameworks that heavily relied on reflection (e.g. Spring). Now the module system doesn't mandate readability edges for reflection (but the target types still must be exported and the defining module must be in the module graph).

    5.2 Reflective readability

    ...

    To make the provider class accessible to the framework we need to make the provider’s module readable by the framework’s module. We could mandate that every framework explicitly add the necessary readability edge to the module graph at run time, as in an earlier version of this document, but experience showed that approach to be cumbersome and a barrier to migration.

    We therefore, instead, revise the reflection API simply to assume that any code that reflects upon some type is in a module that can read the module that defines that type. This enables the above example, and other code like it, to work without change. This approach does not weaken strong encapsulation: A public type must still be in an exported package in order to be accessed from outside its defining module, whether from compiled code or via reflection.