diff --git a/config.toml b/config.toml index e36c23b2..b474b682 100644 --- a/config.toml +++ b/config.toml @@ -24,6 +24,9 @@ description = "Open Source, aber richtig - Open Elements ist ein modernes Untern [markup.goldmark.renderer] unsafe = true +[markup.goldmark.extensions.alerts] +enable = true + [permalinks.page] posts = '/posts/:year/:month/:day/:slug/' [permalinks.section] diff --git a/content/posts/2026-01-27-java-modules-maven4-basics/images/module-structure.svg b/content/posts/2026-01-27-java-modules-maven4-basics/images/module-structure.svg new file mode 100644 index 00000000..766cc3de --- /dev/null +++ b/content/posts/2026-01-27-java-modules-maven4-basics/images/module-structure.svg @@ -0,0 +1 @@ +Text AnalyzerExternal Dependenciesanalyzer.clianalyzer.coreinfo.picocliorg.apache.logging.log4jrequiresrequiresrequiresrequires \ No newline at end of file diff --git a/content/posts/2026-01-27-java-modules-maven4-basics/index.md b/content/posts/2026-01-27-java-modules-maven4-basics/index.md new file mode 100644 index 00000000..d6d0e61d --- /dev/null +++ b/content/posts/2026-01-27-java-modules-maven4-basics/index.md @@ -0,0 +1,275 @@ +--- +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. + +> [!NOTE] +> **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. + +> [!NOTE] +> **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. + +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 `` element in just a single `pom.xml`: + +```xml + + + com.openelements.showcases.analyzer.core + + + com.openelements.showcases.analyzer.cli + + +``` +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//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)). + +> [!NOTE] +> **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. + +> [!NOTE] +> **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 `` 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 `/` +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 `` 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/). diff --git a/layouts/_default/_markup/render-blockquote.html b/layouts/_default/_markup/render-blockquote.html new file mode 100644 index 00000000..0108acb8 --- /dev/null +++ b/layouts/_default/_markup/render-blockquote.html @@ -0,0 +1,34 @@ +{{- if eq .Type "alert" }} +{{- $iconMap := dict + "note" `` + "tip" `` + "important" `` + "warning" `` + "caution" `` +}} +{{- $colorMap := dict + "note" "bg-blue-50 border-blue-400 text-blue-800" + "tip" "bg-green-50 border-green-400 text-green-800" + "important" "bg-purple-50 border-purple-400 text-purple-800" + "warning" "bg-yellow-50 border-yellow-400 text-yellow-800" + "caution" "bg-red-50 border-red-400 text-red-800" +}} +{{- $alertType := .AlertType }} +{{- $icon := index $iconMap $alertType }} +{{- $colors := index $colorMap $alertType }} +
+
+ {{ $icon | safeHTML }} +
+ {{- if .AlertTitle }} +

{{ .AlertTitle | safeHTML }}

+ {{- end }} + {{ .Text | safeHTML }} +
+
+
+{{- else }} +
+ {{ .Text | safeHTML }} +
+{{- end }} diff --git a/netlify.toml b/netlify.toml index 5f82d409..4b26ee1b 100644 --- a/netlify.toml +++ b/netlify.toml @@ -3,7 +3,7 @@ command = "npm run netlify:build" publish = "public" [build.environment] -HUGO_VERSION = "0.118.2" +HUGO_VERSION = "0.134.3" # Production build [context.production]