Example example_patch

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?

Modules in this example

  • Modules: momain, modb

  • In addition, a directory modb-patch

  • modmain has a Main class which is started in run.sh (without patch) or run-patch.sh (with patch)

Module Dependency Graph, created with DepVis

Example’s Module Dependency Graph

Example shows …​

It is possible to patch a module (during compile- and runtime). Use cases e.g.:

  • Whithebox testing: Unit tests need access to internal classes of a package. See example_test for details.

  • Fixing a bug in a third party lib: Prior to modules, it was possible to do this by correct order on the classpath - put the patch iahead of the classes to be patched.

To patch a module, the patching code is compiled resp. started "as if" it were part of a module. The classes of the patch are added to the module (here modb), resp. overwrite existing classes inside the module if the module already contains a class with the same name. See scripts compile-patch, resp. run-patch for details on the options to use.

Note: No JavaDoc generation for this example

Maven 4 Migration

This example has been migrated to Maven 4 using a hybrid compilation approach.

Migration Challenges

Module patching presents a unique challenge for Maven migration.

The Problem

Maven can compile regular modules (modmain and modb). But the patch sources (modb-patch/) must be compiled with --patch-module to compile them as if they were part of the modb 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").

Migration Approach

The Maven 4 migration uses a hybrid compilation strategy.

Phase 1 - Maven compilation of regular modules

Maven 4 compiles the two regular modules (modmain and modb) using the Module Source Hierarchy.

<sources>
  <source>
    <module>modmain</module>
    <directory>src/modmain/main/java</directory>
  </source>
  <source>
    <module>modb</module>
    <directory>src/modb/main/java</directory>
  </source>
</sources>
Phase 2 - Manual compilation of patch sources

The compile.sh script manually compiles the patch sources using javac with --patch-module.

javac --release 11 \
  --patch-module modb=src/modb-patch/main/java \
  --module-path target/classes \
  -d patches/modb \
  src/modb-patch/main/java/pkgb/B.java

Key aspects of this compilation:

  • --release 11 ensures the patch is compiled for the same target as the main modules

  • --patch-module modb=src/modb-patch/main/java treats the patch directory (via symlink) as sources belonging to module modb

  • --module-path target/classes provides access to already-compiled modb classes for dependency resolution (e.g., Helper class)

  • Patch source file accessed via symlink: src/modb-patch/main/java../../../../src/modb-patch

  • This approach maintains full consistency with the src/<name>/main/java directory pattern

  • The patch JAR is created in patchlib/modb.jar (non-modular JAR containing replacement classes)

Runtime Behavior

The run.sh script demonstrates three scenarios.

Without patch

Module runs with original classes from mlib/modb.jar.

With exploded patch classes

--patch-module modb=patches/modb applies patch from directory.

With patch JAR

--patch-module modb=patchlib/modb.jar applies patch from JAR.

All three scenarios produce output that matches the expected golden master result.

Technical Implementation

Symbolic link structure
m4/src/modmain/main/java -> ../../../../src/modmain
m4/src/modb/main/java -> ../../../../src/modb
m4/src/modb-patch/main/java -> ../../../../src/modb-patch

These source symlinks point to the original shared source directories and are checked into Git.

The patch compilation uses --patch-module modb=src/modb-patch/main/java, pointing directly to the symlinked patch sources. This maintains full consistency with the src/<name>/main/java directory pattern throughout the m4/ migration. Dependencies from the original modb module (like the Helper class) are automatically resolved via --module-path target/classes.

Module-path access via mlib

Maven (4) uses maven-jar-plugin with classifiers, creating JARs with names like:

target/example_patch-m4-1.0-SNAPSHOT-modmain.jar
target/example_patch-m4-1.0-SNAPSHOT-modb.jar

However, Java’s --module-path works best with simple JAR names or explicit paths. The run.sh script creates mlib to provide module-path access 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 module system traverses symlinks that were checked out before the target/ directory existed.

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 provide the same functionality: --module-path mlib accesses the Maven-built JARs.

Build artifacts
  • target/ - Maven-built JARs with classifiers (example_patch-m4-1.0-SNAPSHOT-modmain.jar, etc.)

  • target/classes/ - Maven-compiled module classes

  • mlib/ - Symlink to target/ for module-path compatibility

  • patches/modb/ - Exploded patch classes

  • patchlib/modb.jar - Patch JAR

Running the Maven 4 Version

cd m4/
./compile.sh  # Hybrid: Maven for modules, javac for patch
./run.sh      # Run with all three patch scenarios
./verify.sh   # Verify output matches expected result
./clean.sh    # Remove build artifacts

Output

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

Expected Output

I am modb, doing something with pkg.Helper
I am modb, doing something with pkg.Helper
I am doing more because I am PATCHED!
I am modb, doing something with pkg.Helper
I am doing more because I am PATCHED!

Actual Output

I am modb, doing something with pkg.Helper
I am modb, doing something with pkg.Helper
I am doing more because I am PATCHED!
I am modb, doing something with pkg.Helper
I am doing more because I am PATCHED!

Maven 4 Output

I am modb, doing something with pkg.Helper
I am modb, doing something with pkg.Helper
I am doing more because I am PATCHED!
I am modb, doing something with pkg.Helper
I am doing more because I am PATCHED!