|
❗
|
Authors
Originally written by Martin Lehmann, Kristine Schaal and Rüdiger Grammes (cf. original repository). Migrated for JPMS 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. |
This is a example suite for Java 9 jigsaw modules. Many aspects of the new Java 9 Jigsaw modules as defined in Project Jigsaw by JSR 376 and JEP 261.
All the examples have been successfully tested with Windows (64bit), Linux and MacOSX - running the recommended JDK version. Originally developed for Java 9 (which introduced JPMS), the examples are continuously updated for current Java versions.
Build Tool Wrappers: All Maven and Gradle examples now include build tool wrappers, so manual installation of Maven or Gradle is no longer required:
-
Maven examples include Maven Wrapper 3.9.11 (mvnw/mvnw.cmd)
-
Gradle example includes Gradle Wrapper 9.1.0 (gradlew/gradlew.cmd) - requires JDK 17 or later
|
💡
|
Use a local Maven repository to avoid polluting your global one, set a repository location in the top level directory: export MAVEN_OPTS="-Dmaven.repo.local=${PWD}/.m2/repository"Note that this overrides any other settings, e.g., by |
-
Clone this repository
-
Ensure you meet the Minimal Requirements
-
Then either follow the Quick Setup or the Full Setup
-
Finally Run Examples
-
If running on Windows, install a bash, like for example Babun or git bash
-
Install Java JDKs:
-
See Recommended JDK for the recommended version for full CI (automated testing) compatibility
-
For Gradle example: Java 17 or later (required by Gradle 9.x)
-
For cross-compilation example: Java 8
-
|
ℹ️
|
Recommended JDK
The following JDK version is strongly recommended for full compatibility with CI and automated testing: link:.github/workflows/build.yml[role=include]Some examples produce JDK version-specific output and are validated using golden master testing (comparing expected vs. actual output).
To ensure the |
If you want to evaluate your runtime behavior and generate respective graphs, you need depvis.
-
Install GraphViz (>=) 2.38.
-
Install depvis.
Copy and customize the environment template:
cp .envrc-template .envrc
# Edit .envrc and set your JDK and tool paths
# See <<recommended-jdk>> for the recommended versionThen either:
-
Option 1: Source it manually before running examples:
source .envrc -
Option 2: Use direnv to automatically load environment when entering this directory
|
ℹ️
|
SDKMAN Users
This repository includes a |
-
Edit file
env.shto configureJAVA_HOME,JAVA_HOME_JDK8,GRAPHVIZ_HOME, andDEPVIS_HOME(see TODO markers in the file)-
See Recommended JDK for the recommended
JAVA_HOMEvalue
-
-
Also edit file
env.shto configure the path separator. If run on Windows, use \; (a blackslash quoting a ;). If you run all stuff on *nix, use a colon : .
-
Either call one of the scripts on the top level, i.e., one of
allclean.sh,allcompile.sh,allcreatevis.shandallrun.sh(orall.shfor all in one step). -
Alternatively, cd to one of the examples and call one of the scripts there (again
all.shfor all in one step).
Many examples support multiple build approaches in separate subdirectories (e.g., m4/ for Maven 4 migrations).
All orchestration scripts accept an optional <type> parameter to execute builds in these subdirectories:
# Standard behavior - runs examples in example_xyz/
cd jigsaw-examples
./allcompile.sh
./allverify.sh
# Type-specific behavior - runs examples in example_xyz/m4/
./allcompile.sh m4
./allverify.sh m4
./allrun.sh m4
./all.sh m4
# Verify with flags
./allverify.sh m4 --only # Skip rebuild, only verifyAvailable types:
-
m4- Maven 4 migrations using Module Source Hierarchy -
m3- (Future) Maven 3 alternative approaches -
gradle-alt- (Future) Gradle build alternatives
How it works:
-
Without a type parameter: Scripts execute in
example_xyz/(original build) -
With a type parameter: Scripts execute in
example_xyz/<type>/(type-specific build) -
Examples without the specified type directory are automatically skipped
-
Output shows which variant is being executed (e.g., "Compiling example_hiddenmain/m4")
Most examples in this repository use golden master testing (also known as characterization testing or approval testing) to ensure output consistency. This technique captures the expected output of a program and compares it against actual output during subsequent runs.
How it works in this repository:
-
Each example with golden master testing has an
expected-result/run.txtfile containing the captured expected output -
When you run
./verify.shin an example directory, it executes the example and compares the actual output (saved torun-result/run.txt) with the expected output -
The test passes if both outputs match exactly
Why some examples don’t have golden master testing:
Examples that use build tools (Maven, Gradle) or frameworks (Spring Boot) typically produce output that varies between runs. This includes timestamps, absolute file paths, download progress messages, port numbers, and environment-specific information. For these examples, manual verification or integration tests are more appropriate.
All examples have been tested with the recommended JDK version on Windows, Linux, and macOS. The examples were originally developed with Java 9 (which introduced JPMS) and have been continuously updated to work with current LTS versions.
Using Build Tool Wrappers:
-
For Maven examples: Use
./mvnw(Unix/Mac) ormvnw.cmd(Windows) instead ofmvn -
For Gradle example: Use
./gradlew(Unix/Mac) orgradlew.cmd(Windows) instead ofgradle-
Important: Set
JAVA_HOMEto JDK 17+ before running:export JAVA_HOME=/path/to/jdk17(or useJAVA_HOME=… ./gradlew …)
-
|
ℹ️
|
All scripts have been tested with bash only. There might be minor issues with the *.sh scripts whenever they call each other. To be sure, you should use all of these clean, compile, run, test etc. scripts in a bash. |
-
Scripts in top level directory all*.sh call the corresponding scripts recursively
-
Scripts in each example are all.sh, clean.sh, compile*.sh, run*.sh
-
Script createvis.sh in each example creates a GraphViz visualiation, see https://github.com/accso/java9-jigsaw-depvis
-
Source files are in
example_…/src. That’s the module-source-path. -
Compiled .class files go to
example_…/mods. -
JAR files go to
example_…/mlib. That’s the module-path when run. -
Third-party JAR files are in
example_…/amlib. That’s also the module-path, in this case containing automatic modules. -
A few examples do compile "old-style" without modules. Results go to
example_…/classes. -
If a module is patched, then the .class files for the patch are in
example_…/patches. Corresponding JAR files are inexample_…/patchlib.
Many examples have been migrated to Maven 4 using the new Module Source Hierarchy layout.
The Maven 4 builds are located in m4/ subdirectories within each example.
For most examples, the Maven 4 migration follows a straightforward pattern.
Directory Structure Convention: All examples follow Maven 4’s default directory structure pattern src/<module>/<scope>/<lang> as documented in the Maven 4 Model API - Source Element.
This means main sources are located at src/<module>/main/java and test sources at src/<module>/test/java.
-
Create a
pom.xmlwith<modelVersion>4.1.0</modelVersion> -
Use the
<sources>block to declare modules following the default pattern:<build> <sources> <source> <module>modmain</module> <directory>src/modmain/main/java</directory> </source> <!-- Optionle test sources --> <source> <module>modmain</module> <directory>src/modmain/test/java</directory> <scope>test</scope> </source> <!-- Additional modules... --> </sources> </build>
🔥Even though we follow the Maven 4 default pattern, the
<sources>block including explicit<directory>(<scope>for test sources) is currently required. Maven 4 may support implicit sources in future versions. -
Configure the compiler plugin:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>4.0.0-beta-3</version> </plugin>
-
Set the target Java version:
<properties> <maven.compiler.release>11</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties>
This standard approach works for examples where modules have straightforward dependencies and no special compilation requirements.
Some examples require additional configuration beyond the standard approach:
- Automatic module dependencies
-
Examples using automatic modules (like example_automatic-module-logging) need the
maven-dependency-pluginto copy JARs and--module-pathcompiler arguments. - Compiler flags (
--add-reads,--add-exports) -
Examples demonstrating module boundary overrides (like example_addReads_addExports) need
<compilerArgs>configuration. - Classpath code (unnamed module)
-
Examples mixing modular code with classpath code cannot include non-modular code in the
<sources>element. These require a hybrid approach where classpath code is compiled by script with javac and placed incplib/, while Maven compiles the modules.Two main patterns exist:
- Pattern 1: Explicit modules accessing classpath code
-
When explicit modules need to access the unnamed module (classpath), Maven compiles modules with
--add-reads <module>=ALL-UNNAMEDand--class-pathcompiler arguments.Examples:
-
example_unnamed-module_access-from-explicit-module - Basic classpath access
-
example_unnamed-module_access-from-explicit-module-reflection - Reflective access to classpath code
-
- Pattern 2: Classpath code depending on modules
-
When classpath code depends on modules at compile-time, Maven compiles the modules first (with the compile script creating JARs in
mlib/), then classpath code is compiled by the script withjavac -cp mlib/*.Examples:
-
example_unnamed-module-reflection-illegal-access - Classpath code using modules, demonstrates
--illegal-accessflags -
example_unnamed-module_accessing-module-path - Classpath code accessing module-path
-
example_unnamed-module_access-from-automatic-module - Classpath code with automatic modules
-
⚠️ Unsuccessful Classpath Code Handling ApproachesSeveral approaches were attempted before arriving at the hybrid compilation solution:
- exec-maven-plugin approach
-
Calling javac directly via exec-maven-plugin was rejected as too specialized and not a general pattern for Maven 4 migration. It bypasses the Maven compiler plugin entirely and lacks maintainability.
- Dual source directories
-
Attempted using
src/main/javafor classpath code andsrc/<module>/main/javafor modules with separate compiler executions. Failed because the<sources>element is not recognized within plugin execution configurations. - Maven profiles
-
Tried using two profiles to separate classpath and module compilation. Failed because the
<sources>element can only be used at top-level<build>, not within profiles or plugin executions.
The core limitation is that the Module Source Hierarchy assumes all directories under
<sources>are modules with module descriptors. There is no way to designate certain source directories as classpath-only code within the Module Source Hierarchy itself, necessitating the hybrid approach with manual javac compilation. - Automatic modules without module descriptors
-
Examples with automatic modules (JARs without
module-info.java) cannot include these in the<sources>element, as Maven 4’s Module Source Hierarchy requires module descriptors for all entries. These require manual compilation with javac to create JARs, then Maven compiles explicit modules with--module-pathpointing to the automatic module JARs inamlib*/.Examples:
-
example_naming-modules - Four automatic modules demonstrating module naming conventions
-
example_layer-modules-all-in-boot-layer - One automatic module (modauto1) used by explicit modules in boot layer
-
example_splitpackage_automatic-modules - Two automatic modules compiled by script, explicit module uses one via
--module-path
-
- Split packages in explicit modules
-
When multiple explicit modules contain the same package name (split packages), JPMS handles them differently depending on whether the packages are exported and how the modules are loaded.
Examples:
-
example_splitpackage - Two modules (
modsplitbar1andmodsplitbar2) both contain packagepkgbar(not exported). They compile successfully when only one is required, but fail at runtime withLayerInstantiationExceptionwhen both are loaded into the same layer via--add-modules. The foo modules (modsplitfoo1andmodsplitfoo2) cannot compile because they export packagepkgfooandmodmainfoorequires both.
-
- Split packages with automatic modules
-
When an automatic module is on the module-path, it automatically reads all other modules. If multiple modules (automatic or explicit) export the same package, the automatic module encounters a split package violation at compile time. This requires sequential compilation to avoid having modules with split packages on the module-path simultaneously when the automatic module is present.
Examples:
-
example_layer-modules-grouped-in-hierarchy - Two automatic modules with split packages (
pkgversion), requiring five-step compilation wheremodfoois compiled with onlyamlib1/on path, andmodbarwith onlyamlib2/on path -
example_layer-hierarchy - Three explicit modules (
mod.x_bottom,mod.x_middle,mod.x_top) all exportpkgx, causing compile-time conflicts with automatic modulejavax.jsonthat reads all modules (at runtime, layer isolation prevents the conflict)
-
- JDK-specific requirements
-
Examples requiring specific JDK versions may need
forkandexecutableconfiguration with environment-specific paths. Examples requiring specific JDK versions may needforkandexecutableconfiguration with environment-specific paths. - Resource handling
-
Examples with non-.class resources (like example_resources) need
maven-resources-pluginconfiguration to copy resources from the modular source directories (src/<module>/main) to the target classes directories, as the hybrid compilation approach only packages compiled.classfiles by default. - Multiple module versions
-
Examples requiring multiple versions of the same module (like example_layer-modules-module-resolution) cannot use the standard Module Source Hierarchy approach, as the
<sources>element expects one source location per module name. These require a hybrid approach where Maven compiles the primary modules and manual javac compilation handles additional module versions. - Testing with separate test modules (blackbox and whitebox)
-
Examples demonstrating both blackbox and whitebox testing patterns require a hybrid approach when the blackbox test is a separate module. Maven 4’s Module Source Hierarchy automatically handles whitebox testing by detecting test sources with
scope="test"for the same module and applying--patch-moduleduringtest-compile. However, blackbox test modules (with their ownmodule-info.java) cannot be integrated into the same Maven build, requiring javac compilation separately.Examples:
-
example_test - Whitebox testing via Maven 4 automatic
--patch-module, blackbox testing via javac for separate test module
-
- Module patching for bug fixes or replacements
-
Examples demonstrating module patching (using
--patch-moduleto replace or add classes to a module) require a hybrid approach. Maven 4 compiles the regular modules, while patch sources must be compiled manually with javac using--patch-moduleto compile them as if they were part of the target module. Maven 4 does not have built-in support for patch source compilation (unlike its automatic--patch-modulesupport for test sources withscope="test").Examples:
-
example_patch - Hybrid approach: Maven compiles modules, javac compiles patch sources with
--patch-module modb=src/modb-patch/main/java
-
Refer to individual example READMEs for detailed migration notes specific to each example’s requirements.
No software is ready, ever ;-) So here are some ideas left (any other feedback very welcome!):
-
Lessons learned from the migration to Maven 4 (and perhaps to reconsider)
-
❏ Module versions are optional, but perhaps enforced currently by the M4 compiler plugin - check and fix if necessary (allow to drop it)?
-
❏ Maven resources plugin configuration may need to be adapted when modular source code is organized in Module Source Hierarchy (
src/<module>/main) - consider if this should be documented more prominently or if alternative approaches exist.
-
-
❏ Do a bit of renaming and refactoring of module names. (modmain had been moda before, so that’s why most modules are called modb, modc, …)
-
❏ New example for open modules
-
❏ New example for Java agents and instrumentation
-
❏ New example for test coverage when doing whitebox tests
-
❏ Known issues:
ℹ️All examples work with current Java LTS versions (11, 17). The example_spring-hibernate may need updates for optimal Java 17+ compatibility.
The following table includes a Maven 4 Migration Status column indicating the migration status of each example:
- [check circle] Standard
-
Successfully migrated using standard Module Source Hierarchy. No special compilation handling required.
- [exclamation triangle] Special
-
Successfully migrated but requires special handling (hybrid compilation, compiler flags, automatic modules, etc.). See the Maven 4 Migration section above for details.
- [times circle] Blocked
-
Cannot be migrated to Maven 4 Module Source Hierarchy due to technical limitations (currently none).
- [wrench] WIP
-
Work in progress. Migration attempted but not yet complete.
- N/A
-
Not applicable. Example uses Maven/Gradle directly, or migration is not relevant to JPMS features.
Click the status icon to jump to the example’s Maven 4 Migration section for details.
| Examples | Description | Module Dependencies | M4 |
|---|---|---|---|
Examples on the declaration of modules |
|||
Which naming conventions exist for modules? Which names are not allowed? |
|||
How can one specify annotations and deprecation for modules? |
|||
Examples on basic module reads and exports attributes |
|||
How does requires, requires transitive and qualified exports look like? Relates to all other examples in this section. |
|||
How does requires static look like? Relates to all other examples in this section. |
|||
How does requires and exports look like? Relates to all other examples in this section. |
|||
How does requires and qualified exports look like? Relates to all other examples in this section. |
|||
How do reflection calls look like? Relates to all other examples in this section. |
|||
Examples on dynamic binding with uses/provides |
|||
How does uses-provides look like? Relates to all other examples in this section. |
|||
How does uses-provides look like, when uses is separated from the interface? Relates to all other examples in this section. |
|||
Examples on accessibility and (non) exported packages |
|||
What happens, when classes / packages in a module are exported, but their sub/super classes are not? Relates to all other examples in this section. |
|||
What happens, when exceptions are thrown to classes outside the module but their package is not exported? Relates to all other examples in this section. |
|||
What happens, when outside the module a callback implementation is called which package is not exported? Relates to all other examples in this section. |
|||
Examples on specificing add options |
|||
How can we use --add-exports in a manifest file for the Java launcher? Relates to all other examples in this section. |
|||
How can we use --add-reads and --add-exports for Javac compiler and Java launcher Relates to all other examples in this section. |
|||
How can we use --add-reads and --add-exports for reflection calls? Relates to all other examples in this section. |
|||
Examples on automatic modules |
|||
How to automatic modules (for logging) look like? Related example: splitpackage_automatic-modules |
|||
Examples on restricting the access to resources in other modules |
|||
Which resources in modules are accessible, which are not? |
|||
Examples on the split package problem |
|||
What happens when one has a split package problem at compile / at runtime? Relates to all other examples in this section. |
|||
What happens when one Automatic Module automatically reads all other Automatic Modules on the module path and hence creates an unwanted split package problem? Related examples: automatic-module-logging and all other examples in this section. |
|||
Examples on resolution of modules, layers and visibility of modules between layers |
|||
How can an "app server" JerryMouse (sic!) load and start modules, as a kind of module starter/container? Relates to all other examples in this section. |
|||
Which modules are resolved? Usage of jlink Relates to all other examples in this section. |
|||
How can one create a hierarchy of layers automatically and add modules (naming conventions)? Relates to all other examples in this section. |
|||
How does the boot layer look like containing a bunch of modules? Relates to all other examples in this section. |
|||
How does a small hiearchy of layers look like when one explicitely distributes a bunch of modules to these layers? Relates to all other examples in this section. |
|||
How are different versions of a module resolved depending on the setup of the layer? Relates to all other examples in this section. |
|||
Examples on testing |
|||
How can one achieve blackbox and whitebox testing? Relates to all other examples in this section. |
|||
How can one achieve blackbox testing with Maven? Relates to all other examples in this section. |
N/A |
||
How can one achieve whitebox testing with Maven? Relates to all other examples in this section. |
N/A |
||
How can we patch a module at compile / runtime? Relates to all other examples in this section. |
|||
Examples on Main classes |
|||
Is it possible that one can call a Main class which is in a non-exported package? |
|||
Examples on access from and to the classpath (i.e. the unnamed module) |
|||
Can a Automatic Module access the classpath (i.e. the unnamed module)? Relates to all other examples in this section. |
|||
Can a Explicit Module access the classpath (i.e. the unnamed module)? Relates to all other examples in this section. |
|||
Can a Explicit Module access the classpath (i.e. the unnamed module) via reflection? Relates to all other examples in this section. |
|||
Can the classpath (i.e. the unnamed module) access concealed packages in the JDK and what happens when the JDK "kill switch" is activated? Relates to all other examples in this section. |
|||
Can the classpath (i.e. the unnamed module) access modules on the module path? Relates to all other examples in this section. |
|||
Examples on build systems |
|||
How can one use Gradle 9.1.0 for building a modularized project? Includes Gradle Wrapper. Requires JDK 17+. Relates to all other examples in this section. |
N/A |
||
How can one use Maven 3.9.11 for building a modularized project? Includes Maven Wrapper. Relates to all other examples in this section. |
N/A |
||
Examples on porting applications from Java8 to Java9 |
|||
How does the migration of a Spring Boot application with a bunch of Maven plugins look like and where do we have to tweak / change in comparison to Java 8? |
N/A |
||
What happens when one compiles with a modern JDK while targeting Java release 8 for backwards compatibility? |
|||
Examples on non-Jigsaw topics |
|||
How does the new Java 9 version string (cf JEP 223) look like? |
|||
The examples have been used and tested with these tools and libraries (on Windows 10, Linux, MacOSX):
| Tool | Version | Used for | Remark | Link |
|---|---|---|---|---|
JDK |
See Recommended JDK |
all examples at compile and runtime |
Required for golden master testing (verify.sh) |
|
JDK |
17 LTS or later |
Required by Gradle 9.x |
||
JDK |
8 LTS |
only needed for cross-compilation example |
For cross-compilation testing |
|
Maven Wrapper |
3.9.11 |
Original Maven examples |
Included in all (non M4 migrated) Maven examples - no manual installation required |
|
Gradle Wrapper |
9.1.0 |
Gradle example |
Included in example_gradle-project - no manual installation required. Requires JDK 17+ |
https://docs.gradle.org/current/userguide/gradle_wrapper.html |
|
|
|
Deprecated - Use Maven Wrapper instead (./mvnw) |
|
|
|
|
Deprecated - Use Gradle Wrapper instead (./gradlew) |
|
Junit |
4.12 |
all test examples |
together with Hamcrest 1.3 |
|
Spring Boot, various libs |
various |
only in example_spring-hibernate |
refer to POM.xml in this example |
|
GraphViz |
2.38 |
visualizing the module graph |
||
DepVis |
0.3 |
visualizing the module graph, provides the .dot file as input for GraphViz |
|
ℹ️
|
These are the versions with with we have tested the example suite. Older or newer versions might also work but we did not try. |
For a detailed history of changes and migrations in this project, see CHANGELOG.
DepVis, see https://github.com/accso/java9-jigsaw-depvis : Visualization tool for Jigsaw modules
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.




























