diff --git a/content/posts/2026-01-27-java-modules-maven4-basics.md b/content/posts/2026-01-27-java-modules-maven4-basics.md
deleted file mode 100644
index d8b0a3d5..00000000
--- a/content/posts/2026-01-27-java-modules-maven4-basics.md
+++ /dev/null
@@ -1,268 +0,0 @@
----
-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/lego-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.
-
-
-
-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)).
-
-**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 `` 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 address both issues in the [follow-up homework article]({{< relref "posts/2026-02-26-java-modules-maven4-basics-homework" >}}).
-
----
-
-Apache Maven and Maven are trademarks of the [Apache Software Foundation](https://www.apache.org/).
diff --git a/content/posts/2026-02-26-java-modules-maven4-basics-homework.md b/content/posts/2026-02-26-java-modules-maven4-basics-homework.md
deleted file mode 100644
index 5fcbefef..00000000
--- a/content/posts/2026-02-26-java-modules-maven4-basics-homework.md
+++ /dev/null
@@ -1,260 +0,0 @@
----
-outdated: false
-showInBlog: true
-title: "Solving Resource and Packaging Challenges with Maven 4 Modules"
-slug: "java-modules-maven4-basics-homework"
-date: 2026-02-26
-author: gerd
-excerpt: "Maven 4 module source hierarchy is powerful but still maturing. This article explains workarounds for missing resources and JAR packaging until Apache Maven fully supports these features."
-categories: [open-source, support-and-care, maven, java]
-preview_image: "/posts/preview-images/open-source-green.svg"
----
-
-
-In the [previous article]({{< relref "posts/2026-01-27-java-modules-maven4-basics" >}}), we left you with two homework assignments.
-This follow-up explains why things don’t work out of the box and provides workarounds until [Apache Maven](https://maven.apache.org/) 4 fully supports these features.
-
-## The Homework Challenges
-
-If you tried the homework from the previous article, you likely encountered two issues:
-
-1. **Missing log output** – The application runs but doesn’t show the expected logging messages
-2. **JAR packaging problems** - Running `./mvnw package` creates a single JAR that doesn’t work as expected on the module path
-
-Both issues stem from the fact that Maven 4’s module source hierarchy is still evolving.
-While the [Maven Compiler Plugin](https://maven.apache.org/plugins/maven-compiler-plugin-4.x/modules.html) (version 4.0.0-beta-3) already supports this new structure, other parts of the build lifecycle haven’t caught up yet.
-
-## Issue 1: Missing Resources
-
-### What's Happening?
-
-When you run the application after `./mvnw compile`, you might notice that Log4j falls back to its [default configuration](https://logging.apache.org/log4j/2.x/manual/configuration.html#automatic-configuration) instead of using our custom `log4j2.xml`.
-
-Looking at the source structure:
-
-```text
-src/com.openelements.showcases.analyzer.cli/
-├── main/
-│ ├── java/
-│ │ └── ...
-│ └── resources/
-│ └── log4j2.xml ①
-```
-1. Our Log4j configuration file
-
-And the compiled output:
-
-```text
-target/classes/
-├── com.openelements.showcases.analyzer.core/
-│ └── ...
-└── com.openelements.showcases.analyzer.cli/
- └── com/openelements/showcases/analyzer/cli/
- └── AnalyzerCommand.class ①
-```
-1. Notice: no `log4j2.xml` here!
-
-The `log4j2.xml` file is missing from the compiled output.
-
-### The Hint in Maven's Output
-
-If you watch the Maven output carefully during `./mvnw compile`, you’ll notice these messages:
-
-```text
-[INFO] --- resources:3.3.1:resources (default-resources) @ analyzer ---
-[INFO] skip non existing resourceDirectory .../src/main/resources
-[INFO] skip non existing resourceDirectory .../src/main/resources-filtered
-```
-
-This is the hint: Maven’s resources plugin looks for the traditional `src/main/resources/` directory, which doesn’t exist in our project.
-It completely ignores our module-specific resource directories.
-
-### Why It Doesn't Work
-
-Maven’s [standard directory layout](https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html) expects resources in `src/main/resources/`.
-The [module source hierarchy]({{< relref "posts/2026-01-27-java-modules-maven4-basics#the-module-source-hierarchy" >}}) places resources in `src//main/resources/`, but Maven’s core resource handling doesn’t yet recognize this convention.
-
-You can track the fix in [Maven Core PR 11505](https://github.com/apache/maven/pull/11505).
-
-**NOTE:** The Maven team already merged the fixes into the `master` branch, which will become Maven 4.1.
-For Maven 4.0 (e.g., 4.0.0-rc-6), the team needs to backport these changes to the 4.0.x branch.
-Then it will eventually become part of the next Maven release.
-
-
-
-### The Workaround
-
-Until the Maven team releases the fix, we explicitly copy resources using the maven-resources-plugin:
-
-```xml
-
- org.apache.maven.plugins
- maven-resources-plugin
- 3.3.1
-
-
- copy-cli-resources
- process-resources
-
- copy-resources
-
-
- ${project.build.outputDirectory}/com.openelements.showcases.analyzer.cli
-
-
- src/com.openelements.showcases.analyzer.cli/main/resources
-
-
-
-
-
-
-```
-1. Copy to the module’s output directory
-2. From the module’s resource directory
-
-After applying this workaround, run `./mvnw compile` again and verify:
-
-```bash
-ls target/classes/com.openelements.showcases.analyzer.cli/log4j2.xml
-```
-
-Now the application finds the Log4j configuration and logging works as expected.
-
-## Issue 2: JAR Packaging
-
-### What's Happening?
-
-When you run `./mvnw package`, Maven creates a single JAR file:
-
-```text
-target/
-└── analyzer-1.0.0-SNAPSHOT.jar
-```
-
-If you try to use this JAR on the module path:
-
-```bash
-java --module-path "target/analyzer-1.0.0-SNAPSHOT.jar:target/lib" \
- --module com.openelements.showcases.analyzer.cli/...
-```
-
-You’ll get an error because the JAR contains **both** modules combined, which violates Java Module System rules.
-According to the [JAR File Specification](https://docs.oracle.com/en/java/javase/17/docs/specs/jar/jar.html#modular-jar-files), a modular JAR must contain exactly one `module-info.class` at its root.
-
-### Why It Doesn't Work
-
-Maven’s JAR plugin traditionally creates one JAR per Maven module (Maven 3).
-With the module source hierarchy, we have one Maven project but multiple Java modules.
-The default behavior packages everything in `target/classes/` into a single JAR, mixing all contained Java modules.
-
-This creates a broken JAR:
-
-```text
-analyzer-1.0.0-SNAPSHOT.jar
-├── com.openelements.showcases.analyzer.core/
-│ ├── module-info.class <-- First module descriptor
-│ └── ...
-└── com.openelements.showcases.analyzer.cli/
- ├── module-info.class <-- Second module descriptor (conflict!)
- └── ...
-```
-
-The fix requires changes to both [Maven Core PR 11549](https://github.com/apache/maven/pull/11549) and [JAR Plugin PR 508](https://github.com/apache/maven-jar-plugin/pull/508).
-
-### The Workaround
-
-Until the Maven team releases the fix, we configure the JAR plugin to create separate JARs for each Java module:
-
-```xml
-
- org.apache.maven.plugins
- maven-jar-plugin
- 3.4.2
-
-
- default-jar
- none
-
-
- jar-core
- package
-
- jar
-
-
- ${project.build.outputDirectory}/com.openelements.showcases.analyzer.core
- core
-
-
-
- jar-cli
- package
-
- jar
-
-
- ${project.build.outputDirectory}/com.openelements.showcases.analyzer.cli
- cli
-
-
-
-
-```
-1. Disable the default JAR creation
-2. Each execution packages one Java module’s classes
-3. The current JAR plugin requires classifiers to distinguish the respective artifact for each module
-
-After `./mvnw package`, you’ll have:
-
-```text
-target/
-├── analyzer-1.0.0-SNAPSHOT-core.jar
-└── analyzer-1.0.0-SNAPSHOT-cli.jar
-```
-
-### Running from JARs
-
-Now you can run the application from the JARs:
-
-```bash
-java --module-path "target/analyzer-1.0.0-SNAPSHOT-core.jar:\
-target/analyzer-1.0.0-SNAPSHOT-cli.jar:\
-target/lib" \
- --module com.openelements.showcases.analyzer.cli/com.openelements.showcases.analyzer.cli.AnalyzerCommand \
- README.*
-```
-
-**CAUTION:** The classifier suffix (`-core`, `-cli`) is a limitation of the current JAR plugin.
-Once the Maven team releases PR 508, you’ll be able to create properly named JARs like `analyzer-core-1.0.0-SNAPSHOT.jar` without classifiers.
-
-
-
-## Source Code
-
-We committed the above changes to the sample source code repository on [GitHub](https://github.com/support-and-care/maven-modular-sources-showcases).
-Clone it and switch to branch `blog-1-homework`:
-
-```bash
-git clone https://github.com/support-and-care/maven-modular-sources-showcases # unless already done
-cd maven-modular-sources-showcases
-git checkout blog-1-homework
-```
-
-## Summary
-
-Maven 4’s module source hierarchy is a powerful feature, but it’s still maturing.
-The two workarounds we’ve shown address:
-
-| Issue | Cause | Workaround | Tracking |
-| --- | --- | --- | --- |
-| Missing resources | Maven doesn’t copy `src//main/resources/` automatically | Explicit maven-resources-plugin configuration | [PR 11505](https://github.com/apache/maven/pull/11505) |
-| Broken JAR packaging | Single JAR contains multiple Java modules | Explicit maven-jar-plugin executions per module | [PR 11549](https://github.com/apache/maven/pull/11549), [PR 508](https://github.com/apache/maven-jar-plugin/pull/508) |
-
-These workarounds will become unnecessary once the respective Maven releases include the fixes.
-We’ll update this blog series when that happens.
-
----
-
-
-Apache Maven and Maven are trademarks of the [Apache Software Foundation](https://www.apache.org/).
diff --git a/content/posts/2026-03-12-java-modules-encapsulation-internal-packages.md b/content/posts/2026-03-12-java-modules-encapsulation-internal-packages.md
deleted file mode 100644
index 61f72d0f..00000000
--- a/content/posts/2026-03-12-java-modules-encapsulation-internal-packages.md
+++ /dev/null
@@ -1,237 +0,0 @@
----
-outdated: false
-showInBlog: true
-title: "Encapsulation – Hiding Implementation Details with Java Modules"
-slug: "java-modules-encapsulation-internal-packages"
-date: 2026-03-12
-author: gerd
-excerpt: "Java Modules provide strong encapsulation to hide implementation details. Learn how to create internal packages that are invisible to other modules, enforced by both the compiler and the JVM at runtime."
-categories: [open-source, support-and-care, maven, java]
-preview_image: "/posts/preview-images/open-source-green.svg"
----
-
-
-In the [first article]({{< relref "posts/2026-01-27-java-modules-maven4-basics" >}}) (and its [homework extension]({{< relref "posts/2026-02-26-java-modules-maven4-basics-homework" >}})), we built a modular Java project with Maven 4 and exported all packages from our core module.
-But what happens when we have utility code that should stay hidden from other modules?
-Java Modules provide _strong encapsulation_ to solve exactly this problem.
-
-## The Problem: Leaking Implementation Details
-
-In Blog 1, our core module exports both its packages:
-
-```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;
-}
-```
-
-This means every public class in `model` and `service` is part of the module’s public API.
-Other modules can depend on any of these classes, making it hard to change internal implementation details later.
-
-Consider our `TextAnalyzer`: it contains text normalization logic – lowercasing, stripping punctuation, splitting into words – directly in its methods.
-This normalization is an _implementation detail_ that other modules should not depend on.
-If we extract it into a separate utility class, we want to ensure that only our core module can use it.
-
-## Creating an Internal Package
-
-We extract the normalization logic into a new class `TextNormalizer` in a dedicated `internal` package:
-
-```text
-src/com.openelements.showcases.analyzer.core/main/java/
-├── module-info.java
-└── com/openelements/showcases/analyzer/core/
- ├── model/
- │ ├── Document.java
- │ └── Statistics.java
- ├── service/
- │ ├── TextAnalyzer.java
- │ └── DocumentReader.java
- └── internal/ ①
- └── TextNormalizer.java ②
-```
-1. New package for internal implementation classes
-2. Text normalization utility – not exported
-
-The `TextNormalizer` provides two methods:
-
-```java
-public class TextNormalizer {
-
- private static final Pattern NON_WORD_CHARS = Pattern.compile("[^\\p{L}\\p{N}]");
- private static final Pattern WHITESPACE = Pattern.compile("\\s+");
-
- public static String normalize(String text) {
- String cleaned = NON_WORD_CHARS.matcher(text).replaceAll(" ");
- return WHITESPACE.matcher(cleaned).replaceAll(" ").trim().toLowerCase();
- }
-
- public static String[] tokenize(String text) {
- String normalized = normalize(text);
- if (normalized.isEmpty()) {
- return new String[0];
- }
- return WHITESPACE.split(normalized);
- }
-}
-```
-
-The `normalize` method lowercases the text, removes all non-letter/non-digit characters, and collapses whitespace.
-The `tokenize` method normalizes and then splits the text into individual words.
-Both methods use Unicode-aware patterns (`\p{L}`, `\p{N}`) to handle text in any language.
-
-Our `TextAnalyzer` now delegates to `TextNormalizer`:
-
-```java
-import com.openelements.showcases.analyzer.core.internal.TextNormalizer;
-```
-
-```java
- String[] words = TextNormalizer.tokenize(content);
-```
-
-Within the same module, `TextAnalyzer` can freely use `TextNormalizer`.
-The key question is: can _other_ modules access it too?
-
-## Controlling Visibility with module-info.java
-
-Here is the updated module descriptor for our core module:
-
-```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; // ②
- // Note: com.openelements.showcases.analyzer.core.internal is NOT exported // ③
-}
-```
-1. Dependency on Log4j for logging
-2. These packages are exported – other modules can use their public classes
-3. The `internal` package is deliberately _not_ exported
-
-The `internal` package does not appear in any `exports` directive.
-This single omission is what makes `TextNormalizer` invisible to every other module -- despite being a `public` class.
-
-## Compile-Time Protection
-
-What happens if the CLI module tries to import `TextNormalizer`?
-
-```java
-// In AnalyzerCommand.java (CLI module)
-import com.openelements.showcases.analyzer.core.internal.TextNormalizer; // ①
-```
-1. This will not compile
-
-The compiler rejects this with a clear error message:
-
-```text
-error: package com.openelements.showcases.analyzer.core.internal is not
- visible
- (package com.openelements.showcases.analyzer.core.internal is declared
- in module com.openelements.showcases.analyzer.core, which does not
- export it)
-```
-
-The module system enforces encapsulation at compile time.
-You cannot accidentally depend on an internal class – the compiler prevents it.
-
-## Runtime Protection
-
-Java Modules go further than compile-time checks.
-Even at runtime, the module system blocks access to non-exported packages.
-
-If someone tries to use reflection to access `TextNormalizer` from another module:
-
-```java
-// Attempting reflective access from the CLI module
-Class> clazz = Class.forName(
- "com.openelements.showcases.analyzer.core.internal.TextNormalizer"
-);
-```
-
-The runtime throws:
-
-```text
-java.lang.IllegalAccessError: class ... cannot access class
- com.openelements.showcases.analyzer.core.internal.TextNormalizer
- (in module com.openelements.showcases.analyzer.core) because module
- com.openelements.showcases.analyzer.core does not export
- com.openelements.showcases.analyzer.core.internal
-```
-
-This is what _strong encapsulation_ means: the boundary is enforced by the JVM itself, not just by the compiler.
-No workaround, no trick, no reflection hack can bypass it.
-
-**NOTE:** If a module explicitly _opens_ a package (via the `opens` directive), reflective access is permitted.
-This is how frameworks like picocli or Hibernate can access annotated classes.
-But `opens` is an explicit, deliberate choice by the module author – not something an external module can force.
-
-
-
-## Comparison with the Classpath
-
-On the traditional classpath, any `public` class is accessible from anywhere.
-There is no way to hide implementation details at the language level.
-Naming a package `internal` is merely a convention that developers can easily ignore.
-
-| Aspect | Classpath | Module Path |
-| --- | --- | --- |
-| Public class visibility | Accessible from everywhere | Only if the package is exported |
-| Internal packages | Convention only (e.g., naming) | Enforced by compiler and JVM |
-| Reflection access | Always possible | Blocked unless the package is opened |
-| Compile-time enforcement | None | Module system rejects illegal access |
-
-Java Modules provide a fundamental improvement: you can define a clear public API for your module, and _everything else is hidden by default_.
-
-## Updated Module Diagram
-
-With the internal package, our module structure now looks like this:
-
-
-
-The `internal` package is used by `service` within the same module, but the CLI module has no access to it.
-
-## Source Code
-
-The above changes are committed to the sample source code repository on [GitHub](https://github.com/support-and-care/maven-modular-sources-showcases).
-Clone it and switch to branch `blog-2-encapsulation`:
-
-```bash
-git clone https://github.com/support-and-care/maven-modular-sources-showcases # unless already done
-cd maven-modular-sources-showcases
-git checkout blog-2-encapsulation
-```
-
-## Summary
-
-In this article, we have seen:
-
-* How to create internal packages that are hidden from other modules
-* The module system enforces encapsulation at both compile time and runtime
-* Even reflection cannot bypass strong encapsulation (unless explicitly opened)
-* This is a fundamental improvement over the classpath, where `public` means accessible from everywhere
-
-The pattern is straightforward: packages listed in `exports` are your public API.
-Everything else is encapsulated.
-
-## Homework
-
-* **Qualified exports**\
-Try changing the `module-info.java` to use a _qualified export_:
-
- ```java
- exports com.openelements.showcases.analyzer.core.internal
- to com.openelements.showcases.analyzer.cli;
- ```
-
- This makes the `internal` package accessible only to the CLI module, but not to any other module.
- When would this be useful?
- We will explore this further in a future article.
-
----
-
-
-Apache Maven and Maven are trademarks of the [Apache Software Foundation](https://www.apache.org/).
diff --git a/content/posts/2026-03-26-java-modules-separating-contract-and-implementation.md b/content/posts/2026-03-26-java-modules-separating-contract-and-implementation.md
deleted file mode 100644
index 78704a18..00000000
--- a/content/posts/2026-03-26-java-modules-separating-contract-and-implementation.md
+++ /dev/null
@@ -1,309 +0,0 @@
----
-outdated: false
-showInBlog: true
-title: "Separating Contract and Implementation with Java Modules"
-slug: "java-modules-separating-contract-and-implementation"
-date: 2026-03-26
-author: gerd
-excerpt: "Java Modules let you separate domain types from their implementation. Learn how to extract an API module and use requires transitive for implied readability, so consumers get access to contract types automatically."
-categories: [open-source, support-and-care, maven, java]
-preview_image: "/posts/preview-images/open-source-green.svg"
----
-
-
-The [previous article]({{< relref "posts/2026-03-12-java-modules-encapsulation-internal-packages" >}}) showed how to hide implementation details using internal packages.
-The core module still mixes two concerns, though: it exports both the domain model (`Document`, `Statistics`) and the service implementation (`TextAnalyzer`).
-Consumers that only need the data types must depend on the entire implementation.
-Java Modules provide an elegant solution: separate the domain types into their own module and use `requires transitive` to keep things convenient.
-
-## The problem: mixed concerns
-
-After the [previous article]({{< relref "posts/2026-03-12-java-modules-encapsulation-internal-packages" >}}), the core module looks like this:
-
-```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;
-}
-```
-
-The module exports both `model` — domain types — and `service` — implementation.
-Any module that needs `Document` or `Statistics` must depend on `core` and transitively pulls in Log4j and the internal implementation.
-
-This represents a common antipattern in modular design: mixing the _contract_ — what the module promises — with the _implementation_ — how it fulfills that promise.
-
-## Introducing a contract module
-
-The solution is a classic layering pattern: extract the domain types into a dedicated _API module_.
-The implementation module then _depends on_ the API and provides concrete service classes.
-
-
-
-The project now has three Java modules:
-
-* **analyzer.api**\
-Pure contract — domain types, no dependencies
-* **analyzer.core**\
-Implementation — depends on API transitively, provides service classes
-* **analyzer.cli**\
-Consumer — depends on core, gains access to API types automatically via `requires transitive`
-
-## The contract module
-
-The new module contains the domain records `Document` and `Statistics`, moved from `core.model`.
-
-### Module descriptor
-
-```java
-module com.openelements.showcases.analyzer.api {
- exports com.openelements.showcases.analyzer.api; // ①
-}
-```
-1. Single export — all API types live in one package
-
-The API module has _no dependencies_.
-It is a pure contract that any module can depend on without pulling in implementation details.
-
-The `Document` and `Statistics` types moved from `com.openelements.showcases.analyzer.core.model` to `com.openelements.showcases.analyzer.api`.
-
-**NOTE:** In the [first]({{< relref "posts/2026-01-27-java-modules-maven4-basics" >}}) and [previous]({{< relref "posts/2026-03-12-java-modules-encapsulation-internal-packages" >}}) articles, a separate `DocumentReader` class in the core module handled reading files from disk.
-With the API/core split, `DocumentReader` remains in the core module — and any module that needs to read a `Document` from disk must depend on core.
-This becomes a problem when consumers should depend only on the API.
-The clean alternative — a `DocumentReader` interface in the API module with an implementation in core — would require its own service wiring and add complexity.
-A pragmatic solution: move the file-reading logic into `Document` itself as factory methods.
-The `Document` record gains `fromPath(Path)` for the common UTF-8 case, and `fromPath(Path, Charset)` for reading with a specific character encoding.
-```java
- /**
- * Reads a document from the given path using UTF-8 encoding.
- *
- * @param path the path to the file
- * @return a new Document instance
- * @throws IOException if the file cannot be read
- */
- public static Document fromPath(Path path) throws IOException {
- return fromPath(path, StandardCharsets.UTF_8);
- }
- /**
- * Reads a document from the given path using the specified charset.
- *
- * @param path the path to the file
- * @param charset the charset to use for reading
- * @return a new Document instance
- * @throws IOException if the file cannot be read
- */
- public static Document fromPath(Path path, Charset charset) throws IOException {
- if (!Files.exists(path)) {
- throw new IOException("File not found: " + path);
- }
- if (!Files.isRegularFile(path)) {
- throw new IOException("Not a regular file: " + path);
- }
- String content = Files.readString(path, charset);
- return new Document(path, content);
- }
-```
-This keeps the API module self-contained without introducing additional classes or service interfaces.
-One trade-off: the old `DocumentReader` logged file paths via Log4j before and after reading.
-The API module has no logging dependency, so these diagnostic messages are gone.
-Callers that need logging can add it at the call site.
-
-
-
-## The updated core module
-
-The core module now _uses_ the API types rather than _defining_ them.
-
-### Module descriptor
-
-```java
-module com.openelements.showcases.analyzer.core {
- requires transitive com.openelements.showcases.analyzer.api; // ①
- requires org.apache.logging.log4j; // ②
-
- exports com.openelements.showcases.analyzer.core.service; // ③
- // Note: com.openelements.showcases.analyzer.core.internal is NOT exported // ④
-}
-```
-1. `requires transitive` — any module that requires `core` automatically reads `api`
-2. Log4j is an implementation detail, required but not transitive
-3. Only the service package is exported
-4. The internal package remains encapsulated
-
-The key change is `requires transitive com.openelements.showcases.analyzer.api`.
-This means the API types appear in core’s exported signatures — `TextAnalyzer.analyze(Document)` returns `Statistics` — so consumers of core automatically need access to the API module.
-The `transitive` keyword makes this explicit and automatic.
-
-The `TextAnalyzer` class itself is unchanged — it still delegates to the internal `TextNormalizer` encapsulated in the [previous article]({{< relref "posts/2026-03-12-java-modules-encapsulation-internal-packages" >}}).
-Only its imports changed from `core.model.Document` to `api.Document`, and likewise for `Statistics`.
-
-## How `requires transitive` works
-
-The command-line module’s descriptor has _not changed_ from the [previous article]({{< relref "posts/2026-03-12-java-modules-encapsulation-internal-packages" >}}):
-
-```java
-module com.openelements.showcases.analyzer.cli {
- requires com.openelements.showcases.analyzer.core;
- requires info.picocli;
- requires org.apache.logging.log4j;
-
- opens com.openelements.showcases.analyzer.cli to info.picocli;
-}
-```
-
-The command-line module declares `requires com.openelements.showcases.analyzer.core` — and because core declares `requires transitive com.openelements.showcases.analyzer.api`, it can use `Document` and `Statistics` without an explicit `requires api` directive.
-
-This is called _implied readability_: the transitive keyword propagates the dependency through the module graph.
-
-Without `transitive`, the command-line module would need to declare:
-
-```java
-module com.openelements.showcases.analyzer.cli {
- requires com.openelements.showcases.analyzer.core;
- requires com.openelements.showcases.analyzer.api; // ①
- requires info.picocli;
- requires org.apache.logging.log4j;
-
- opens com.openelements.showcases.analyzer.cli to info.picocli;
-}
-```
-1. Would be required without `transitive` on core’s dependency
-
-### What breaks without `transitive`?
-
-If you remove the `transitive` keyword from core’s module-info.java:
-
-```java
-module com.openelements.showcases.analyzer.core {
- requires com.openelements.showcases.analyzer.api; // no transitive!
- // ...
-}
-```
-
-The command-line module will fail to compile:
-
-```text
-error: package com.openelements.showcases.analyzer.api is not visible
- (package com.openelements.showcases.analyzer.api is declared in module
- com.openelements.showcases.analyzer.api, which is not in the module graph)
-```
-
-The compiler tells you exactly what’s wrong: the API module is not in the command-line module’s graph because core no longer transitively exports it.
-
-## The updated project structure
-
-With three modules, the directory structure looks like this:
-
-```text
-src/
-├── com.openelements.showcases.analyzer.api/ ①
-│ └── main/java/
-│ ├── module-info.java
-│ └── com/openelements/showcases/analyzer/api/
-│ ├── Document.java
-│ └── Statistics.java
-├── com.openelements.showcases.analyzer.core/ ②
-│ └── main/java/
-│ ├── module-info.java
-│ └── com/openelements/showcases/analyzer/core/
-│ ├── internal/
-│ │ └── TextNormalizer.java
-│ └── service/
-│ └── TextAnalyzer.java
-└── com.openelements.showcases.analyzer.cli/ ③
- └── main/java/
- ├── module-info.java
- └── com/openelements/showcases/analyzer/cli/
- └── AnalyzerCommand.java
-```
-1. API module — domain types, no dependencies
-2. Core module — implementation, depends on API transitively
-3. Command-line module — consumer, unchanged module descriptor
-
-## Updated POM configuration
-
-The Maven POM now declares three module sources:
-
-```xml
-
-
- com.openelements.showcases.analyzer.api
-
-
- com.openelements.showcases.analyzer.core
-
-
- com.openelements.showcases.analyzer.cli
-
-
-```
-1. The API module — domain types
-2. The core module — implementation
-3. The command-line module
-
-Maven compiles them in dependency order: api first — no dependencies — then core — depends on api — then cli — depends on core.
-
-## Source Code
-
-The above changes are committed to the sample source code repository on [GitHub](https://github.com/support-and-care/maven-modular-sources-showcases).
-Clone it and switch to branch `blog-3-api-impl`:
-
-```bash
-git clone https://github.com/support-and-care/maven-modular-sources-showcases # unless already done
-cd maven-modular-sources-showcases
-git checkout blog-3-api-impl
-```
-
-## Building and running
-
-As described in the [first article]({{< relref "posts/2026-01-27-java-modules-maven4-basics" >}}), compile and prepare the dependencies:
-
-```bash
-./mvnw prepare-package
-```
-
-Then run the application:
-
-```bash
-java --module-path "target/classes:target/lib" \
- --module com.openelements.showcases.analyzer.cli/com.openelements.showcases.analyzer.cli.AnalyzerCommand \
- README.*
-```
-
-The output is unchanged from the [previous article]({{< relref "posts/2026-03-12-java-modules-encapsulation-internal-packages" >}}) — the API extraction is an internal restructuring that does not affect runtime behavior.
-
-## Summary
-
-This article covered:
-
-* How to separate domain types into a dedicated API module
-* The `requires transitive` directive provides _implied readability_ — consumers of core automatically get access to API types
-* Domain types (`Document`, `Statistics`) belong in the API module — along with file-reading logic via `Document.fromPath()`
-* Service classes (`TextAnalyzer`) remain in the core module
-* The command-line module’s descriptor is unchanged — `requires transitive` handles the wiring
-
-This separation brings a clear architectural benefit: any future module can depend on just the API without pulling in the implementation.
-
-However, you may have noticed that the command-line module still directly depends on the core module to instantiate `TextAnalyzer`.
-The next article addresses this by introducing the _Service Provider Interface_ pattern.
-Using `uses`, `provides`, and `ServiceLoader`, the command-line module will depend _only_ on the API module — achieving true inversion of control where the consumer no longer needs to know the implementation at all.
-
-## Homework
-
-* **Remove `transitive` and fix the build**\
-Remove the `transitive` keyword from core’s `requires api` declaration and observe the compilation error.
-Then add an explicit `requires com.openelements.showcases.analyzer.api;` to the command-line module to fix it.
-Which approach do you prefer, and why?
-* **Add a second consumer module**\
-Create a test module that imports only `Document` and `Statistics` from the API.
-Does it need to depend on core?
-What happens if it does — does it also get access to `TextAnalyzer`?
-* **Preview: Inversion of Control**\
-Right now the command-line module still instantiates `new TextAnalyzer(...)` directly, coupling it to the implementation.
-Can you imagine a way to discover the analyzer at runtime so the command-line module only needs `requires api`?
-The next article explores this with `ServiceLoader`.
-
----
-
-Apache Maven and Maven are trademarks of the [Apache Software Foundation](https://www.apache.org/).