Skip to content

support-and-care/java9-jigsaw-examples

 
 

Repository files navigation

Java 9 Jigsaw modules - example suite

Authors

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.

What is this about?

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 mvn_settings.xml.

Setup

  1. Clone this repository

  2. Ensure you meet the Minimal Requirements

  3. Then either follow the Quick Setup or the Full Setup

  4. Finally Run Examples

Minimal Requirements

  1. If running on Windows, install a bash, like for example Babun or git bash

  2. Install Java JDKs:

Optional Software

If you want to evaluate your runtime behavior and generate respective graphs, you need depvis.

  1. Install GraphViz (>=) 2.38.

  2. Install depvis.

Quick Start

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 version

Then 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 .sdkmanrc file that automatically configures the recommended JDK version when you have SDKMAN with sdkman_auto_env=true enabled. This ensures full compatibility with CI and golden master testing (verify.sh scripts). However, be aware that SDKMAN’s automatic version switching may interfere with JAVA_HOME settings in .envrc or env.sh. If you experience version conflicts, either disable sdkman_auto_env or ensure your .envrc/env.sh matches the recommended version.

Traditional Setup (Deprecated)

  1. Edit file env.sh to configure JAVA_HOME, JAVA_HOME_JDK8, GRAPHVIZ_HOME, and DEPVIS_HOME (see TODO markers in the file)

  2. Also edit file env.sh to configure the path separator. If run on Windows, use \; (a blackslash quoting a ;). If you run all stuff on *nix, use a colon : .

Running Examples

  • Either call one of the scripts on the top level, i.e., one of allclean.sh, allcompile.sh, allcreatevis.sh and allrun.sh (or all.sh for all in one step).

  • Alternatively, cd to one of the examples and call one of the scripts there (again all.sh for all in one step).

Type-Specific Builds

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 verify

Available 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")

Golden Master Testing

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.txt file containing the captured expected output

  • When you run ./verify.sh in an example directory, it executes the example and compares the actual output (saved to run-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.

Additional information

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) or mvnw.cmd (Windows) instead of mvn

  • For Gradle example: Use ./gradlew (Unix/Mac) or gradlew.cmd (Windows) instead of gradle

    • Important: Set JAVA_HOME to JDK 17+ before running: export JAVA_HOME=/path/to/jdk17 (or use JAVA_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.

Overall conventions

  1. Scripts in top level directory all*.sh call the corresponding scripts recursively

  2. Scripts in each example are all.sh, clean.sh, compile*.sh, run*.sh

  3. Script createvis.sh in each example creates a GraphViz visualiation, see https://github.com/accso/java9-jigsaw-depvis

  4. Source files are in example_…​/src. That’s the module-source-path.

  5. Compiled .class files go to example_…​/mods.

  6. JAR files go to example_…​/mlib. That’s the module-path when run.

  7. Third-party JAR files are in example_…​/amlib. That’s also the module-path, in this case containing automatic modules.

  8. A few examples do compile "old-style" without modules. Results go to example_…​/classes.

  9. If a module is patched, then the .class files for the patch are in example_…​/patches. Corresponding JAR files are in example_…​/patchlib.

Maven 4 Migration

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.

Standard Migration Approach and Defaults

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.

  1. Create a pom.xml with <modelVersion>4.1.0</modelVersion>

  2. 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.

  3. Configure the compiler plugin:

    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>4.0.0-beta-3</version>
    </plugin>
  4. 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.

Special Cases

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-plugin to copy JARs and --module-path compiler 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 in cplib/, 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-UNNAMED and --class-path compiler arguments.

Examples:

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 with javac -cp mlib/*.

Examples:

⚠️
Unsuccessful Classpath Code Handling Approaches

Several 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/java for classpath code and src/<module>/main/java for 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-path pointing to the automatic module JARs in amlib*/.

Examples:

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 (modsplitbar1 and modsplitbar2) both contain package pkgbar (not exported). They compile successfully when only one is required, but fail at runtime with LayerInstantiationException when both are loaded into the same layer via --add-modules. The foo modules (modsplitfoo1 and modsplitfoo2) cannot compile because they export package pkgfoo and modmainfoo requires 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 where modfoo is compiled with only amlib1/ on path, and modbar with only amlib2/ on path

  • example_layer-hierarchy - Three explicit modules (mod.x_bottom, mod.x_middle, mod.x_top) all export pkgx, causing compile-time conflicts with automatic module javax.json that reads all modules (at runtime, layer isolation prevents the conflict)

JDK-specific requirements

Examples requiring specific JDK versions may need fork and executable configuration with environment-specific paths. Examples requiring specific JDK versions may need fork and executable configuration with environment-specific paths.

Resource handling

Examples with non-.class resources (like example_resources) need maven-resources-plugin configuration to copy resources from the modular source directories (src/<module>/main) to the target classes directories, as the hybrid compilation approach only packages compiled .class files 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-module during test-compile. However, blackbox test modules (with their own module-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-module to 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-module to 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-module support for test sources with scope="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.

TODOs, LOP, Backlog, Ideas, …​

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.

Example Overview

Maven 4 Migration Status Legend

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

naming-modules

Which naming conventions exist for modules? Which names are not allowed?

[exclamation triangle]

annotations

How can one specify annotations and deprecation for modules?

moduledependencies

[check circle]

Examples on basic module reads and exports attributes

requires_exports_requires-transitive_exports-to

How does requires, requires transitive and qualified exports look like?

Relates to all other examples in this section.

moduledependencies

[check circle]

requires-static

How does requires static look like?

Relates to all other examples in this section.

moduledependencies

[check circle]

requires_exports

How does requires and exports look like?

Relates to all other examples in this section.

moduledependencies

[check circle]

requires_exports-to

How does requires and qualified exports look like?

Relates to all other examples in this section.

moduledependencies

[check circle]

reflection

How do reflection calls look like?

Relates to all other examples in this section.

moduledependencies

[check circle]

Examples on dynamic binding with uses/provides

uses-provides

How does uses-provides look like?

Relates to all other examples in this section.

moduledependencies

[check circle]

uses-provides_uses-in-client

How does uses-provides look like, when uses is separated from the interface?

Relates to all other examples in this section.

moduledependencies

[check circle]

Examples on accessibility and (non) exported packages

derived_private-package-protected

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.

moduledependencies

[check circle]

exceptions

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.

moduledependencies

[check circle]

interface-callback

What happens, when outside the module a callback implementation is called which package is not exported?

Relates to all other examples in this section.

moduledependencies

[check circle]

Examples on specificing add options

addExports_manifest

How can we use --add-exports in a manifest file for the Java launcher?

Relates to all other examples in this section.

moduledependencies

[exclamation triangle]

addReads_addExports

How can we use --add-reads and --add-exports for Javac compiler and Java launcher

Relates to all other examples in this section.

moduledependencies

[exclamation triangle]

addReads_addExports_reflection

How can we use --add-reads and --add-exports for reflection calls?

Relates to all other examples in this section.

moduledependencies

[exclamation triangle]

Examples on automatic modules

automatic-module-logging

How to automatic modules (for logging) look like?

Related example: splitpackage_automatic-modules

moduledependencies

[exclamation triangle]

Examples on restricting the access to resources in other modules

resources

Which resources in modules are accessible, which are not?

moduledependencies

[exclamation triangle]

Examples on the split package problem

splitpackage

What happens when one has a split package problem at compile / at runtime?

Relates to all other examples in this section.

moduledependencies

[exclamation triangle]

splitpackage_automatic-modules

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.

moduledependencies

[exclamation triangle]

Examples on resolution of modules, layers and visibility of modules between layers

jerrymouse

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.

[exclamation triangle]

resolved-modules

Which modules are resolved? Usage of jlink

Relates to all other examples in this section.

moduledependencies

[check circle]

layer-hierarchy

How can one create a hierarchy of layers automatically and add modules (naming conventions)?

Relates to all other examples in this section.

layer hierarchy.drawio

[exclamation triangle]

layer-modules-all-in-boot-layer

How does the boot layer look like containing a bunch of modules?

Relates to all other examples in this section.

moduledependencies

[exclamation triangle]

layer-modules-grouped-in-hierarchy

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.

moduledependencies

[exclamation triangle]

layer-modules-module-resolution

How are different versions of a module resolved depending on the setup of the layer?

Relates to all other examples in this section.

[exclamation triangle]

Examples on testing

test

How can one achieve blackbox and whitebox testing?

Relates to all other examples in this section.

moduledependencies

[exclamation triangle]

maven-test-blackbox

How can one achieve blackbox testing with Maven?

Relates to all other examples in this section.

N/A

maven-test-whitebox

How can one achieve whitebox testing with Maven?

Relates to all other examples in this section.

N/A

patch

How can we patch a module at compile / runtime?

Relates to all other examples in this section.

moduledependencies

[exclamation triangle]

Examples on Main classes

hiddenmain

Is it possible that one can call a Main class which is in a non-exported package?

moduledependencies

[check circle]

Examples on access from and to the classpath (i.e. the unnamed module)

unnamed-module_access-from-automatic-module

Can a Automatic Module access the classpath (i.e. the unnamed module)?

Relates to all other examples in this section.

[exclamation triangle]

unnamed-module_access-from-explicit-module

Can a Explicit Module access the classpath (i.e. the unnamed module)?

Relates to all other examples in this section.

moduledependencies

[exclamation triangle]

unnamed-module_access-from-explicit-module-reflection

Can a Explicit Module access the classpath (i.e. the unnamed module) via reflection?

Relates to all other examples in this section.

moduledependencies

[exclamation triangle]

unnamed-module-reflection-illegal-access

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.

moduledependencies

[exclamation triangle]

unnamed-module_accessing-module-path

Can the classpath (i.e. the unnamed module) access modules on the module path?

Relates to all other examples in this section.

moduledependencies

[exclamation triangle]

Examples on build systems

gradle-project

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.

moduledependencies

N/A

maven-project

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

spring-hibernate

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

compile-target-jdk8

What happens when one compiles with a modern JDK while targeting Java release 8 for backwards compatibility?

[exclamation triangle]

Examples on non-Jigsaw topics

version

How does the new Java 9 version string (cf JEP 223) look like?

[check circle]

Overview on Tools and Libs

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)

https://adoptium.net/ or https://sdkman.io/

JDK

17 LTS or later

Gradle example

Required by Gradle 9.x

https://adoptium.net/ or https://sdkman.io/

JDK

8 LTS

only needed for cross-compilation example

For cross-compilation testing

https://adoptium.net/temurin/releases/?version=8

Maven Wrapper

3.9.11

Original Maven examples

Included in all (non M4 migrated) Maven examples - no manual installation required

https://maven.apache.org/wrapper/

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

Maven

3.5.2, 3.6.1

Maven examples

Deprecated - Use Maven Wrapper instead (./mvnw)

https://maven.apache.org/download.cgi

Gradle

4.2.1, 4.6, 5.4.1

Gradle example

Deprecated - Use Gradle Wrapper instead (./gradlew)

https://github.com/gradle/gradle

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

https://www.graphviz.org/

DepVis

0.3

visualizing the module graph, provides the .dot file as input for GraphViz

https://github.com/accso/java9-jigsaw-depvis

ℹ️

These are the versions with with we have tested the example suite. Older or newer versions might also work but we did not try.

Changelog

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

License

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.

Releases

No releases published

Packages

No packages published

Languages

  • Shell 68.9%
  • Java 31.1%