Java 9 Modules - Qualified Exports

[Updated: Oct 22, 2017, Created: Oct 21, 2017]

Java 9 Module System allows a package to be exported to one or more specific modules, but not to others. To achieve that we need to use exports .. to clause:

module my.module{
  exports my.package to other.module, another.module;
}

This feature was needed to avoid exposing internal packages to all modules while allowing them to be accessed by only selected friendly modules. For example, JDK java.base has many packages which should not be exposed to everyone. Following is a relevant snippet of the java.base module:

module java.base {
   ............
    exports com.sun.security.ntlm to java.security.sasl;
    exports jdk.internal to jdk.jfr;
    exports jdk.internal.jimage to jdk.jlink;
    exports jdk.internal.jimage.decompressor to jdk.jlink;
    exports jdk.internal.jmod to
        jdk.compiler,
        jdk.jlink;
    exports jdk.internal.loader to
        java.instrument,
        java.logging;
    exports jdk.internal.logger to java.logging;
    exports jdk.internal.math to java.desktop;
    .....................
}

Example

In this example, we are going to create three modules. The second module will export a package only 'to' the first module. The third module which will require both these two modules, cannot access the package which it is not qualified for.

We are going to use multi-module directory structure (tutorial here) so that we don't have to compile each module individually.

The First Module

core.ui/ui/widget/WidgetController.java

package ui.widget;

public class WidgetController {
  public void display(String str){
      System.out.println(str);
  }
}

core.ui/module-info.java

module core.ui {
  exports ui.widget;
}

The Second Module

core.util/util/internal/Util.java

package util.internal;

public class Util {
    public static void showString(String str) {
        System.out.println(str);
    }
}

core.util/util/common/StringUtil.java

package util.common;

import java.util.Arrays;
import java.util.List;

public class StringUtil {
    public static List<String> split(String str, String expr) {
        return Arrays.asList(str.split(expr));
    }
}

core.util/module-info.java

module core.util {
    exports util.common;
    exports util.internal to core.ui;
}

As seen above, the package 'util.common' is exported without any qualifications (so it will be accessible to all other modules) while 'util.internal' is only exported to a qualified module 'core.ui'.

The Third Module

main.app/com/example/Main.java

package com.example;

import ui.widget.WidgetController;
import util.common.StringUtil;
import java.util.List;

public class Main {
  public static void main(String[] args) {
      WidgetController wc = new WidgetController();
      wc.display("a test string");

      List<String> list = StringUtil.split("a,b", ",");
      System.out.println(list);
  }
}

main.app/module-info.java

module main.app {
  requires core.ui;
  requires core.util;
}

Here is our project's directory structure.

D:\qualified-exports>tree /A /F
\---src
+---core.ui
| | module-info.java
| |
| \---ui
| \---widget
| WidgetController.java
|
+---core.util
| | module-info.java
| |
| \---util
| +---common
| | StringUtil.java
| |
| \---internal
| Util.java
|
\---main.app
| module-info.java
|
\---com
\---example
Main.java

Compiling

Let's save the list of all java classes in a file:

D:\qualified-exports>dir  /B  /S  src\*.java > javaFiles.txt

Confirm our file has all java source files:

D:\qualified-exports>type javaFiles.txt
D:\qualified-exports\src\core.ui\module-info.java
D:\qualified-exports\src\core.ui\ui\widget\WidgetController.java
D:\qualified-exports\src\core.util\module-info.java
D:\qualified-exports\src\core.util\util\common\StringUtil.java
D:\qualified-exports\src\core.util\util\internal\Util.java
D:\qualified-exports\src\main.app\module-info.java
D:\qualified-exports\src\main.app\com\example\Main.java

Compiling now:

D:\qualified-exports>javac -d outDir --module-source-path src @javaFiles.txt

After compilation:

D:\qualified-exports>tree /A /F
| javaFiles.txt
|
+---outDir
| +---core.ui
| | | module-info.class
| | |
| | \---ui
| | \---widget
| | WidgetController.class
| |
| +---core.util
| | | module-info.class
| | |
| | \---util
| | +---common
| | | StringUtil.class
| | |
| | \---internal
| | Util.class
| |
| \---main.app
| | module-info.class
| |
| \---com
| \---example
| Main.class
|
\---src
+---core.ui
| | module-info.java
| |
| \---ui
| \---widget
| WidgetController.java
|
+---core.util
| | module-info.java
| |
| \---util
| +---common
| | StringUtil.java
| |
| \---internal
| Util.java
|
\---main.app
| module-info.java
|
\---com
\---example
Main.java

Running

D:\qualified-exports>java --module-path outDir --module main.app/com.example.Main
a test string
[a, b]

Using --describe-module:

D:\qualified-exports>java --module-path outDir  --describe-module core.ui
core.ui file:///D:/qualified-exports/outDir/core.ui/
exports ui.widget
requires java.base mandated
D:\qualified-exports>java --module-path outDir  --describe-module core.util
core.util file:///D:/qualified-exports/outDir/core.util/
exports util.common
requires java.base mandated
qualified exports util.internal to core.ui
D:\qualified-exports>java --module-path outDir  --describe-module main.app
main.app file:///D:/qualified-exports/outDir/main.app/
requires core.util
requires java.base mandated
requires core.ui
contains com.example

Using jdeps

jdeps can be used to see all package level dependencies:

D:\qualified-exports>jdeps  --module-path outDir --module main.app
main.app
[file:///D:/qualified-exports/outDir/main.app/]
requires core.ui
requires core.util
requires mandated java.base (@9.0.1)
main.app -> core.ui
main.app -> core.util
main.app -> java.base
com.example -> java.io java.base
com.example -> java.lang java.base
com.example -> java.util java.base
com.example -> ui.widget core.ui
com.example -> util.common core.util

As seen above, the package util.internal is not in com.example dependencies list.

Attempting to access unqualified package

Let's see what will happen if our Main class (in main.app module) attempts to use the unqualified package's classes (in util.internal package):

main.app/com/example/Main.java

package com.example;

import ui.widget.WidgetController;
import util.common.StringUtil;
import util.internal.Util;
import java.util.List;

public class Main {
  public static void main(String[] args) {
      Util.showString("a test String");
  }
}

Compiling:

D:\qualified-exports>javac -d outDir --module-source-path src @javaFiles.txt
D:\qualified-exports\src\main.app\com\example\Main.java:5: error: package util.internal is not visible
import util.internal.Util;
^
(package util.internal is declared in module core.util, which does not export it to module main.app)
1 error

Example Project

Dependencies and Technologies Used :

  • JDK 9.0.1
Java 9 Modules - Qualified Exports Example Select All Download
  • qualified-exports
    • src
      • core.ui
        • ui
          • widget
      • core.util
        • util
          • common
          • internal
      • main.app
        • com
          • example

See Also