Example example_layer-modules-grouped-in-hierarchy

Part of the full Java 9 Jigsaw modules example suite.

Authors

Migrated for Java Modules support documentation of Apache MavenTM in the course of the Maven Support & Care program by Gerd Aschemann (and other team members) as forked repository. Please add discussions, requirements, bugfixes, etc. to the fork instead of the original.

What is this example about?

This example is the same as this example. The main difference is, that the modules are not all in the boot layer but distributed over a layer hierarchy of the boot layers and 3 child layers.

Layers in this example

We have the boot layer as the parent layer for these three child layers:

  • foo layer #1 which contains the module modfoo which requires the automatic module modauto1

  • bar layer #1 which contains the module modbar which requires the automatic module modauto2

  • bar layer #2 which contains the module modbar which requires the automatic module modauto2

The boot layer contains these modules

  • modcommon which has standard functionality used by all other modules

  • modmain which does reflective calls to modbar and modfoo

Module Dependency Graph, created with DepVis

Example’s Module Dependency Graph

Note that this graph has been changed manually as DepVis cannot deal with layers for now.

Example shows …​

The examples shows what happens, if modmain/pkgmain.Main does reflective calls to classes in module modfoo or modbar, respectively. Note that these reflective calls from modmain to modfoo and modbar are done from the boot layer to one of its child layers.

More remarks:

  • Reflective calls from the parent layer (from modmain to modfoo and modbar) are possible.

  • Reflective calls from the parent layer (modfoo and modbar to modcommon) would also be possible.

  • modcommon is used from modmain and modfoo and modbar. So it is resolved in the boot layer already.

  • The child layers do not have a split package problem, though in bar layer #1 and bar layer #2 the same module modbar is resolved.

  • Also modbar and modfoo can use different version of the automatic module modauto1|2. No split package problem.

The interesting parts are all done in modmain/pkgmain.Main here in this example. See its functionality on

  • …​ how to create child layers and add modules

  • …​ how to call functionality in child layers

Output

This example uses golden master testing to ensure output consistency. The expected output is compared with actual output using verify.sh.

Expected Output

Infos for Layer and Module which contain pkgmain.Main, id=ID_Main_19
Layer (java.lang.ModuleLayer), boot layer
Layer's parents: none, as this is the boot layer
Layer's list of modules:
java.base, java.compiler, java.datatransfer, java.desktop, java.logging, java.management, java.management.rmi, java.naming, java.prefs, java.rmi, java.security.jgss, java.security.sasl, java.smartcardio, java.xml, java.xml.crypto, jdk.charsets, jdk.compiler, jdk.crypto.cryptoki, jdk.crypto.ec, jdk.internal.opt, jdk.jartool, jdk.javadoc, jdk.jdeps, jdk.jfr, jdk.jlink, jdk.localedata, jdk.management, jdk.management.jfr, jdk.naming.dns, jdk.naming.rmi, jdk.security.auth, jdk.security.jgss, jdk.unsupported.desktop, jdk.zipfs, modcommon, modmain
Layer's configuration with its list of resolved modules:
java.base, java.compiler, java.datatransfer, java.desktop, java.logging, java.management, java.management.rmi, java.naming, java.prefs, java.rmi, java.security.jgss, java.security.sasl, java.smartcardio, java.xml, java.xml.crypto, jdk.charsets, jdk.compiler, jdk.crypto.cryptoki, jdk.crypto.ec, jdk.internal.opt, jdk.jartool, jdk.javadoc, jdk.jdeps, jdk.jfr, jdk.jlink, jdk.localedata, jdk.management, jdk.management.jfr, jdk.naming.dns, jdk.naming.rmi, jdk.security.auth, jdk.security.jgss, jdk.unsupported.desktop, jdk.zipfs, modcommon, modmain

Create a new 'foo layer #1' as a child of the current layer...
Reflective call to pkgfoo.Foo in modfoo in 'foo layer #1'...
Infos for Layer and Module which contain pkgfoo.Foo, id=ID_Main_74, using automatic module version V1
Layer (java.lang.ModuleLayer), not boot layer
Layer's parents: Parent is boot layer
Layer's list of modules:
modauto1, modfoo
Layer's configuration with its list of resolved modules:
modauto1, modfoo
pkgfoo.Foo, id=ID_Main_74, using automatic module version V1

Create a new 'bar layer #1' as a child of the current layer...
Reflective call to pkgbar.Bar in modbar in 'bar layer #1'...
Infos for Layer and Module which contain pkgbar.Bar, id=ID_Main_74, using automatic module version V2
Layer (java.lang.ModuleLayer), not boot layer
Layer's parents: Parent is boot layer
Layer's list of modules:
modauto2, modbar
Layer's configuration with its list of resolved modules:
modauto2, modbar
pkgbar.Bar, id=ID_Main_74, using automatic module version V2

Create a new 'bar layer #2' as a child of the current layer...
Reflective call to pkgbar.Bar in modbar in 'bar layer #2'...
Infos for Layer and Module which contain pkgbar.Bar, id=ID_Main_74, using automatic module version V2
Layer (java.lang.ModuleLayer), not boot layer
Layer's parents: Parent is boot layer
Layer's list of modules:
modauto2, modbar
Layer's configuration with its list of resolved modules:
modauto2, modbar
pkgbar.Bar, id=ID_Main_74, using automatic module version V2

Actual Output

Infos for Layer and Module which contain pkgmain.Main, id=ID_Main_19
Layer (java.lang.ModuleLayer), boot layer
Layer's parents: none, as this is the boot layer
Layer's list of modules:
java.base, java.compiler, java.datatransfer, java.desktop, java.logging, java.management, java.management.rmi, java.naming, java.prefs, java.rmi, java.security.jgss, java.security.sasl, java.smartcardio, java.xml, java.xml.crypto, jdk.charsets, jdk.compiler, jdk.crypto.cryptoki, jdk.crypto.ec, jdk.internal.opt, jdk.jartool, jdk.javadoc, jdk.jdeps, jdk.jfr, jdk.jlink, jdk.localedata, jdk.management, jdk.management.jfr, jdk.naming.dns, jdk.naming.rmi, jdk.security.auth, jdk.security.jgss, jdk.unsupported.desktop, jdk.zipfs, modcommon, modmain
Layer's configuration with its list of resolved modules:
java.base, java.compiler, java.datatransfer, java.desktop, java.logging, java.management, java.management.rmi, java.naming, java.prefs, java.rmi, java.security.jgss, java.security.sasl, java.smartcardio, java.xml, java.xml.crypto, jdk.charsets, jdk.compiler, jdk.crypto.cryptoki, jdk.crypto.ec, jdk.internal.opt, jdk.jartool, jdk.javadoc, jdk.jdeps, jdk.jfr, jdk.jlink, jdk.localedata, jdk.management, jdk.management.jfr, jdk.naming.dns, jdk.naming.rmi, jdk.security.auth, jdk.security.jgss, jdk.unsupported.desktop, jdk.zipfs, modcommon, modmain

Create a new 'foo layer #1' as a child of the current layer...
Reflective call to pkgfoo.Foo in modfoo in 'foo layer #1'...
Infos for Layer and Module which contain pkgfoo.Foo, id=ID_Main_74, using automatic module version V1
Layer (java.lang.ModuleLayer), not boot layer
Layer's parents: Parent is boot layer
Layer's list of modules:
modauto1, modfoo
Layer's configuration with its list of resolved modules:
modauto1, modfoo
pkgfoo.Foo, id=ID_Main_74, using automatic module version V1

Create a new 'bar layer #1' as a child of the current layer...
Reflective call to pkgbar.Bar in modbar in 'bar layer #1'...
Infos for Layer and Module which contain pkgbar.Bar, id=ID_Main_74, using automatic module version V2
Layer (java.lang.ModuleLayer), not boot layer
Layer's parents: Parent is boot layer
Layer's list of modules:
modauto2, modbar
Layer's configuration with its list of resolved modules:
modauto2, modbar
pkgbar.Bar, id=ID_Main_74, using automatic module version V2

Create a new 'bar layer #2' as a child of the current layer...
Reflective call to pkgbar.Bar in modbar in 'bar layer #2'...
Infos for Layer and Module which contain pkgbar.Bar, id=ID_Main_74, using automatic module version V2
Layer (java.lang.ModuleLayer), not boot layer
Layer's parents: Parent is boot layer
Layer's list of modules:
modauto2, modbar
Layer's configuration with its list of resolved modules:
modauto2, modbar
pkgbar.Bar, id=ID_Main_74, using automatic module version V2

Maven 4 Output

Infos for Layer and Module which contain pkgmain.Main, id=ID_Main_19
Layer (java.lang.ModuleLayer), boot layer
Layer's parents: none, as this is the boot layer
Layer's list of modules:
java.base, java.compiler, java.datatransfer, java.desktop, java.logging, java.management, java.management.rmi, java.naming, java.prefs, java.rmi, java.security.jgss, java.security.sasl, java.smartcardio, java.xml, java.xml.crypto, jdk.charsets, jdk.compiler, jdk.crypto.cryptoki, jdk.crypto.ec, jdk.internal.opt, jdk.jartool, jdk.javadoc, jdk.jdeps, jdk.jfr, jdk.jlink, jdk.localedata, jdk.management, jdk.management.jfr, jdk.naming.dns, jdk.naming.rmi, jdk.security.auth, jdk.security.jgss, jdk.unsupported.desktop, jdk.zipfs, modcommon, modmain
Layer's configuration with its list of resolved modules:
java.base, java.compiler, java.datatransfer, java.desktop, java.logging, java.management, java.management.rmi, java.naming, java.prefs, java.rmi, java.security.jgss, java.security.sasl, java.smartcardio, java.xml, java.xml.crypto, jdk.charsets, jdk.compiler, jdk.crypto.cryptoki, jdk.crypto.ec, jdk.internal.opt, jdk.jartool, jdk.javadoc, jdk.jdeps, jdk.jfr, jdk.jlink, jdk.localedata, jdk.management, jdk.management.jfr, jdk.naming.dns, jdk.naming.rmi, jdk.security.auth, jdk.security.jgss, jdk.unsupported.desktop, jdk.zipfs, modcommon, modmain

Create a new 'foo layer #1' as a child of the current layer...
Reflective call to pkgfoo.Foo in modfoo in 'foo layer #1'...
Infos for Layer and Module which contain pkgfoo.Foo, id=ID_Main_74, using automatic module version V1
Layer (java.lang.ModuleLayer), not boot layer
Layer's parents: Parent is boot layer
Layer's list of modules:
modauto1, modfoo
Layer's configuration with its list of resolved modules:
modauto1, modfoo
pkgfoo.Foo, id=ID_Main_74, using automatic module version V1

Create a new 'bar layer #1' as a child of the current layer...
Reflective call to pkgbar.Bar in modbar in 'bar layer #1'...
Infos for Layer and Module which contain pkgbar.Bar, id=ID_Main_74, using automatic module version V2
Layer (java.lang.ModuleLayer), not boot layer
Layer's parents: Parent is boot layer
Layer's list of modules:
modauto2, modbar
Layer's configuration with its list of resolved modules:
modauto2, modbar
pkgbar.Bar, id=ID_Main_74, using automatic module version V2

Create a new 'bar layer #2' as a child of the current layer...
Reflective call to pkgbar.Bar in modbar in 'bar layer #2'...
Infos for Layer and Module which contain pkgbar.Bar, id=ID_Main_74, using automatic module version V2
Layer (java.lang.ModuleLayer), not boot layer
Layer's parents: Parent is boot layer
Layer's list of modules:
modauto2, modbar
Layer's configuration with its list of resolved modules:
modauto2, modbar
pkgbar.Bar, id=ID_Main_74, using automatic module version V2

Maven 4 Migration

This example required a special migration approach due to the presence of automatic modules with split packages and module-specific dependencies.

Standard Migration

The base modules (modcommon, modmain) were migrated using the standard Module Source Hierarchy approach with direct paths to the original source directories.

Special Handling: Automatic Modules with Split Packages

Problem

Maven 4’s Module Source Hierarchy requires module descriptors (module-info.java) for all source entries. This example contains modauto1 and modauto2, which are automatic modules without module-info.java.

Additionally:

  • Both automatic modules have split packages (pkgversion) which conflict at compile-time

  • modfoo requires modauto1 while modbar requires modauto2

  • Maven 4 compiles all modules together with the same --module-path, causing split package conflicts

    The original approach compiles each explicit module separately with different module-path settings to avoid conflicts.

Solution

Hybrid compilation approach in m4/compile.sh:

  1. Manual javac compilation of modauto1 → amlib1/ and modauto2 → amlib2/

    Each automatic module is compiled separately and packaged as a JAR without module-info.java.

  2. Maven compiles base modules (modcommon, modmain) without automatic modules on path

    These modules don’t depend on the automatic modules.

  3. Manual javac compilation of modfoo with --module-path mlib:amlib1

    Only modauto1 is on module-path, avoiding split package conflicts.

  4. Manual javac compilation of modbar with --module-path mlib:amlib2

    Only modauto2 is on module-path, avoiding split package conflicts.

  5. Runtime uses only mlib/ (automatic modules not needed at runtime for this example)

This hybrid approach is necessary because:

  • Maven 4’s Module Source Hierarchy cannot compile code without module-info.java

  • Automatic modules must be compiled and packaged before explicit modules that depend on them

  • Split packages between automatic modules prevent compiling all modules together

  • Each module requiring an automatic module must be compiled separately with only the needed automatic module on path

  • This example demonstrates module layers where different modules use different automatic module versions

Module-Path Access via mlib for Dynamic Module Loading

The Java code in modmain/pkgmain/Main.java uses the ModuleLayer API to dynamically load modules at runtime from hardcoded paths:

// Dynamically creates layers and loads modules from ./foomlib and ./barmlib
Module fooModule = fooLayer.findModule("modfoo").get();
Module barModule = barLayer.findModule("modbar").get();

Maven (4) creates JARs in the target/ directory with classifier-based names:

target/example_layer-modules-grouped-in-hierarchy-m4-1.0-SNAPSHOT-modcommon.jar
target/example_layer-modules-grouped-in-hierarchy-m4-1.0-SNAPSHOT-modmain.jar
...

To maintain compatibility with the shared Java source code that references mlib, the run.sh script creates mlib using OS-specific approaches:

Unix/macOS

Creates symlink mlib → target (using ln -sfn)

Windows

Creates directory mlib/ and copies JARs from target/

The Windows approach avoids AccessDeniedException issues when Java’s ModuleLayer API traverses symlinks.

A committed symlink m4/mlib → target exists in Git for convenience on Unix systems. On Windows, this symlink is replaced with a directory and copied JARs at runtime by run.sh. Both approaches redirect the hardcoded mlib path to Maven-built JARs without modifying shared source code.