-
Notifications
You must be signed in to change notification settings - Fork 15
Add blog post: Java Modules with Maven 4 (Part 1) #256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ascheman
wants to merge
1
commit into
main
Choose a base branch
from
feature/blog-java-modules-maven4
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
1 change: 1 addition & 0 deletions
1
content/posts/2026-01-27-java-modules-maven4-basics/images/module-structure.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
270 changes: 270 additions & 0 deletions
270
content/posts/2026-01-27-java-modules-maven4-basics/index.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,270 @@ | ||
| --- | ||
| outdated: false | ||
| showInBlog: true | ||
| title: "Your First Modular Java Project with Apache Maven 4" | ||
| date: 2026-01-27 | ||
| author: gerd | ||
| excerpt: "Apache Maven 4 introduces a new way to structure multi-module Java projects using the module source hierarchy. Instead of spreading your code across multiple Maven modules with separate POMs, you can now define all Java modules in a single POM." | ||
| categories: [open-source, support-and-care, maven, java] | ||
| preview_image: "/posts/preview-images/open-source-green.svg" | ||
| --- | ||
|
|
||
|
|
||
| [Apache Maven](https://maven.apache.org/) 4 introduces a new way to structure multi-module Java projects using the _module source hierarchy_. | ||
| Instead of spreading your code across multiple Maven modules with separate POMs, you can now define all Java modules in a single POM. | ||
|
|
||
| The complete source code is available on [GitHub](https://github.com/support-and-care/maven-modular-sources-showcases) – clone it and follow along! | ||
|
|
||
| ## Managing Complexity in Large Applications | ||
|
|
||
| As Java applications grow, managing complexity in terms of class and package dependencies becomes a significant challenge. | ||
| To keep code maintainable, developers often divide their applications into modules with clear boundaries and responsibilities. | ||
|
|
||
| Traditionally, this modularization could hardly be enforced in a monolithic codebase. | ||
| Developers relied on conventions, package naming schemes, and code reviews to maintain structure. | ||
| Over time, tools like [ArchUnit](https://www.archunit.org/) emerged. | ||
| It can verify architectural rules at test time and thus enforce the structure at build time. | ||
|
|
||
| ### Modularization by the build system | ||
|
|
||
| A more structural approach is to split the codebase into separate Maven subprojects (called _modules_ in Maven 3), each with its own `pom.xml`. | ||
|
|
||
| While this enforces boundaries at build time, it introduces new complexity: managing multiple build units, coordinating internal dependencies, and dealing with the overhead of a multi-module Maven or Gradle reactor build. | ||
|
|
||
| ### Modularization by the language | ||
|
|
||
| Java 9 introduced _[Java Modules](https://openjdk.org/jeps/261)_, providing modularization directly at the language level. | ||
| With `module-info.java` descriptors, modules can declare explicit dependencies and control which packages are accessible to other modules. | ||
|
|
||
| **Terminology:** | ||
|
|
||
|
|
||
| The official name is _Java Platform Module System_, but we use the more accessible term _Java Modules_ throughout this series. | ||
| Some people also refer to it as _JPMS_, but this was never an official abbreviation and the main author, Mark Reinhold, [heavily discourages using it](https://www.youtube.com/watch?v=ny4CqBX_kaQ&t=2062s). | ||
| Java 9 introduced JPMS as part of [Project Jigsaw](https://openjdk.org/projects/jigsaw/). | ||
| So you may also find references to Jigsaw in related documents and samples. | ||
|
|
||
| The following terms are essential when working with Java Modules: | ||
|
|
||
| * **`module`**\ | ||
| A named, self-describing collection of packages with explicit dependencies and exports. | ||
| * **`requires`**\ | ||
| Declares a dependency on another module. | ||
| * **`exports`**\ | ||
| Makes a package accessible to other modules. | ||
| * **`module-info.java`**\ | ||
| The module descriptor file that defines the module’s name, dependencies, and exports. | ||
|
|
||
|
|
||
| ### Maven 4 to the rescue | ||
|
|
||
| Maven 4 combines both approaches perfectly. | ||
| With the new _module source hierarchy_, you can define multiple Java modules within a single Maven project, all sharing one `pom.xml`. | ||
| This gives you the benefits of Java Modules encapsulation without the overhead of managing multiple Maven modules. | ||
|
|
||
| **Maven 4's Innovation: Multiple Java Modules in a Single Project:** | ||
|
|
||
|
|
||
| Both Maven 3 and [Gradle](https://docs.gradle.org/current/samples/sample_java_modules_multi_project.html) support compiling and running Java modules. | ||
| However, they require each Java module to be a separate build unit – a Maven subproject (called _module_ in Maven 3) or Gradle subproject with its own build configuration. | ||
| Maven 4 renamed these build units from _modules_ to _subprojects_ to avoid confusion with Java Modules. | ||
|
|
||
| Moreover, Maven 4 introduces the _module source hierarchy_, allowing multiple Java modules to coexist within a single Maven project, sharing one `pom.xml`. | ||
| This eliminates the overhead of managing multiple build files while still benefiting from Java module encapsulation. | ||
|
|
||
|
|
||
| This blog article starts a series exploring these new opportunities. | ||
| We’ll begin with a simple example and progressively add more advanced Java Modules features in later posts. | ||
|
|
||
| ## The Sample Application | ||
|
|
||
| Our showcase is a simple Text Analyzer that reads text files and produces statistics like word count, line count, and word frequency. | ||
| We split the application into two Java modules: a **core** module containing the domain model and analysis services, and a **cli** module providing a command-line interface using [picocli](https://picocli.info/). | ||
| Both modules use [Log4j 2](https://logging.apache.org/log4j/) for logging, which is itself a fully modular library. | ||
|
|
||
| <img src="images/module-structure.svg" alt="Module dependencies"> | ||
|
|
||
| For readability, the diagram uses shortened module names (`analyzer.cli`, `analyzer.core`) corresponding to the fully qualified modules `com.openelements.showcases.analyzer.cli` and `com.openelements.showcases.analyzer.core`. | ||
|
|
||
| ## The Module Source Hierarchy | ||
|
|
||
| With Maven 4’s `modelVersion` 4.1.0, you can declare multiple Java modules using the `<sources>` element in just a single `pom.xml`: | ||
|
|
||
| ```xml | ||
| <sources> | ||
| <source> | ||
| <module>com.openelements.showcases.analyzer.core</module> <!--1--> | ||
| </source> | ||
| <source> | ||
| <module>com.openelements.showcases.analyzer.cli</module> <!--2--> | ||
| </source> | ||
| </sources> | ||
| ``` | ||
| 1. The core module containing domain model and services | ||
| 2. The CLI module providing the command-line interface | ||
|
|
||
| This tells Maven where to find your Java modules. | ||
| The source code follows a specific directory structure: | ||
|
|
||
| ```text | ||
| src/ | ||
| ├── com.openelements.showcases.analyzer.core/ ① | ||
| │ └── main/java/ | ||
| │ ├── module-info.java | ||
| │ └── com/openelements/showcases/analyzer/core/ | ||
| └── com.openelements.showcases.analyzer.cli/ ② | ||
| └── main/java/ | ||
| ├── module-info.java | ||
| └── com/openelements/showcases/analyzer/cli/ | ||
| ``` | ||
| 1. This is the module source directory for the core module | ||
| 2. This is the module source directory for the CLI module | ||
|
|
||
| Each Java module contains a `module-info.java` at the root of its source directory (e.g., `src/<module>/main/java/module-info.java`). | ||
| This file is the module descriptor that defines the module’s name, its dependencies, and which packages it exposes to other modules (cf. [Defining Module Dependencies](#defining-module-dependencies)). | ||
|
|
||
| **Module Names and Directory Names:** | ||
|
|
||
|
|
||
| You’ll notice that the module names (e.g., `com.openelements.showcases.analyzer.core`) match the directory names under `src/`. | ||
| This is a convention that helps keep things organized, but the module name in `module-info.java` can be different from the directory name if needed. | ||
|
|
||
| This may look redundant and cumbersome at first, in particular as we have long module names which duplicate the contained package name hierarchies. | ||
| For the sake of clarity, we’ll keep them similar at the beginning of this series. | ||
|
|
||
| Note that the module name (as well as Java package names) use a dot-separated format, while the directory structure uses slashes (`/`). | ||
| Over time, we may introduce other opportunities to name directories and modules differently. | ||
|
|
||
|
|
||
| ## Defining Module Dependencies | ||
|
|
||
| Each Java module needs a `module-info.java` that declares its dependencies and exports. | ||
| Here’s the core module, which currently exports all its packages. | ||
| In future blog posts, we will extend this module and demonstrate how to encapsulate implementation details by keeping certain packages internal. | ||
|
|
||
| ```java | ||
| module com.openelements.showcases.analyzer.core { | ||
| requires org.apache.logging.log4j; // ① | ||
|
|
||
| exports com.openelements.showcases.analyzer.core.model; // ② | ||
| exports com.openelements.showcases.analyzer.core.service; // ② | ||
| } | ||
| ``` | ||
| 1. Declare dependency on Log4j for logging | ||
| 2. Export packages that other modules can use | ||
|
|
||
| The `exports` directive makes packages visible to other modules. | ||
| Packages not exported are _encapsulated_ – they cannot be accessed from outside the module, even via reflection. | ||
|
|
||
| **Module Dependencies and Maven Dependencies:** | ||
|
|
||
|
|
||
| The `requires` directive in `module-info.java` declares compile-time and runtime dependencies at the Java module level. | ||
| However, you still need to declare these libraries as `<dependency>` elements in your `pom.xml` so Maven can download and manage them. | ||
| The module system enforces boundaries; Maven provides the artifacts. | ||
|
|
||
|
|
||
| ## Building and Running | ||
|
|
||
| ### Compiling the Modules | ||
|
|
||
| Build the project with Maven: | ||
|
|
||
| ```bash | ||
| ./mvnw compile | ||
| ``` | ||
|
|
||
| After compilation, explore the `target/classes` directory. | ||
| You’ll find that Maven creates a separate output directory for each Java module: | ||
|
|
||
| ```text | ||
| target/classes/ | ||
| ├── com.openelements.showcases.analyzer.core/ | ||
| │ ├── module-info.class | ||
| │ └── com/openelements/showcases/analyzer/core/ | ||
| │ ├── model/ | ||
| │ └── service/ | ||
| └── com.openelements.showcases.analyzer.cli/ | ||
| ├── module-info.class | ||
| └── com/openelements/showcases/analyzer/cli/ | ||
| ``` | ||
|
|
||
| Each module directory is an _exploded module_ – a directory containing compiled classes along with its `module-info.class`. | ||
| This structure allows the Java runtime to load each module separately from the module path. | ||
|
|
||
| ### External Dependencies | ||
|
|
||
| The application depends on Log4j 2 and picocli, which are modular libraries. | ||
| For the sake of simplicity, we will copy the dependencies to a `target/lib` directory. | ||
|
|
||
| Copy dependencies to `target/lib` for the module path: | ||
|
|
||
| ```bash | ||
| ./mvnw prepare-package | ||
| ``` | ||
|
|
||
| ### Running the Application | ||
|
|
||
| Run the application using the module path: | ||
|
|
||
| ```bash | ||
| java --module-path "target/classes:target/lib" \ | ||
| --module com.openelements.showcases.analyzer.cli/com.openelements.showcases.analyzer.cli.AnalyzerCommand \ | ||
| README.md | ||
| ``` | ||
| 1. The module path contains: | ||
| * The exploded modules from the `target/classes/` directory | ||
| * The external dependencies from the `target/lib/` directory (all contained JARs) | ||
| 2. The main class to run, specified as `<module>/<fully-qualified-class-name>` | ||
| 3. Input file to analyze | ||
|
|
||
| On Windows, you need to use a semicolon (`;`) instead of a colon (`:`) to separate paths in the `--module-path` argument. | ||
|
|
||
| **IMPORTANT: Module Path Instead of Classpath** | ||
|
|
||
|
|
||
| Note that we do not specify the classpath anymore when using modules. | ||
| All dependencies and modules must be available on the module path. | ||
| This is a fundamental change when working with modular Java applications. | ||
|
|
||
| The classpath is a flat list of JARs and directories where Java searches for classes. | ||
| It provides no encapsulation – any public class can access any other public class. | ||
|
|
||
| The module path can contain modular JARs (with `module-info.class`), exploded module directories, or directories containing modular JARs. | ||
| The Java runtime uses module descriptors to enforce boundaries: only exported packages are accessible, and only declared dependencies can be used. | ||
|
|
||
|
|
||
| Using the module path leads to a significant improvement in runtime dependency management, as the Java runtime can enforce module boundaries and dependencies at runtime. | ||
|
|
||
| ## Security Benefits | ||
|
|
||
| This also brings security benefits. | ||
| Java’s original Security Manager and `AccessController` APIs – designed to sandbox untrusted code – were complex, rarely used correctly, and carried performance overhead. | ||
| [Java 17 set them to _deprecated_](https://openjdk.org/jeps/411) and [Java 24 removed them entirely](https://openjdk.org/jeps/486). | ||
| Java Modules provide a simpler, more effective security model through _strong encapsulation_: The runtime truly hides internal packages and prevents all kinds of access even via reflection, unless explicitly opened. | ||
| This prevents libraries and frameworks from accessing private implementation details of your code or the JDK itself. | ||
|
|
||
| ## Summary | ||
|
|
||
| In this first article, we’ve seen: | ||
|
|
||
| * Maven 4’s module source hierarchy with `<sources>` element | ||
| * Basic `module-info.java` with `requires` and `exports` | ||
| * Building and running modular applications | ||
|
|
||
| ## Homework | ||
|
|
||
| * **Check the expected output**\ | ||
| If you look into the source code and the dependencies and execute the application, you might notice some missing output. | ||
| If you take a close look at the Maven output and the resulting target directory structure, you may find clues on what is missing. | ||
| * **Try to package the modules as JAR and run it**\ | ||
| You may try to package the application as a JAR file and run it from there by executing `./mvnw package` (a JAR file will appear in the `target` folder). | ||
| What do you observe when trying to run the JAR file by replacing the `target/classes` path with the JAR file path in the `--module-path` argument? | ||
|
|
||
| We will address both issues in one of the next articles. | ||
|
|
||
| --- | ||
|
|
||
| This article is published on the [Open Elements blog](https://open-elements.com/posts/2026/01/27/java-modules-maven4-basics/). | ||
|
|
||
| Apache Maven and Maven are trademarks of the [Apache Software Foundation](https://www.apache.org/). | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.