Part-4: JPMS/Java Modules - project build tools (Maven)

Over the previous java tutorial sections dedicated to to the Java Platform Module System (JPMS) was described the term “MODULE” in the meaning of “Strong Encapsulation” of the packages. The “Strong Encapsulation” in JPMS addresses the visibility and usage of the packages available inside the module path.

The following section is focused on building a project using a “standard” Maven build  tool.

Maven comes also with the term “MODULE”. The “maven modules” have a different goal. The “Maven Module” helps to organize the project/application to the several sub-projects (“Maven Modules”). Such “Maven Modules” provide an artifacts with their own package structure.

Shortly: “Java Module provides a strong-encapsulation of the packages and Maven Module helps to organize the Project Structure

Simple Maven Example with JUnit-5

The following example introduces a small modularized example Maven project with one “Maven Module”. The goal is to execute all related JUnit tests agains the developed logic. It is important to keep in mind the tutorial section dedicated to the “Java Module” directives and types: Part-2: JPMS/Java Modules – descriptor, directives, types. The example project “java-modules-junit5” uses, for the successful run, the approach provided by the directive “open”. The “open” directive allows the reflective access to the all packages of the module at the run-time.

Remember that such approach is not probably what is intended, there are required a few maven related changes according to the module visibility previously mentioned, but it is out of the scope of this tutorial.

The example maven project has the following structure:

java-modules-junit5
├── main
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   │   ├── com
│       │   │   │   └── wengnerits
│       │   │   │       └── modules
│       │   │   │           └── example
│       │   │   │               ├── Main.java
│       │   │   │               └── producer
│       │   │   │                   └── SimpleProducer.java
│       │   │   └── module-info.java
│       │   └── resources
│       └── test
│           └── java
│               └── com
│                   └── wengnerits
│                       └── modules
│                           └── example
│                               ├── MainTest.java
│                               └── producer
│                                   └── SimpleProducerTests.java
├── pom.xml 

The project has the parent pom.xml Maven descriptor that provides information about the project and configuration details used by Maven to build an artifact. The parent pom.xml example:

 
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wengnerits.modules</groupId>
    <artifactId>java-modules-junit5-parent</artifactId>
    <modules>
        <module>main</module>
    </modules>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <java.version>15</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        ..
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-engine</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-api</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <pluginManagement>
            …
    </build>
</project> 

The created “Maven module” main also contains the pom.xml Maven descriptor file. It contains the following content:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>java-module-junit5-parent</artifactId>
        <groupId>com.wengnerits.modules</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>main</artifactId>
    <name>java-module-junit5: main</name>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project> 

The “main” Maven module defines the Java Modulemain” and “exports” the package. The java module descriptor example:

 
module main {
    exports com.wengnerits.modules.example;
} 

The “main” module defines the package structure (see above) and classes Main.java and SimpleProducer.java.

Let’s create a JUnit tests (MainTest.java: 2 test and SimpleProducerTests: 1 test) for those classes respecting the maven directory standard (see above):

 
public class MainTest {
    @Test
    public void mainValidTest(){
        assertTrue(Main.valid());
    }

    @Test
    void mainMessageTest(){
        assertEquals("Perfect", Main.message());
    }
}

class SimpleProducerTests {
    @Test
    void listProducer() {
        List<String> expectedList = Arrays.asList("Test", "Me", "!");
        SimpleProducer producer = new SimpleProducer();

        List<String> list = producer.listProducer("Test", "Me", "!");

        assertEquals(expectedList, list);
    }
}
 

The artifacts seems to be properly managed by the descriptor files (pom.xml). Execute the following program inside the project root:

 
$mvn isntall
output: 
[[INFO] Running com.wengnerits.modules.example.producer.SimpleProducerTests
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.02 s <<< FAILURE! - in com.wengnerits.modules.example.producer.SimpleProducerTests
[ERROR] com.wengnerits.modules.example.producer.SimpleProducerTests.listProducer  Time elapsed: 0.003 s  <<< ERROR!
java.lang.reflect.InaccessibleObjectException: Unable to make com.wengnerits.modules.example.producer.SimpleProducerTests() accessible: module main does not "opens com.wengnerits.modules.example.producer" to unnamed module @c86b9e3

[INFO] Running com.wengnerits.modules.example.MainTest
[ERROR] Tests run: 2, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.028 s <<< FAILURE! - in com.wengnerits.modules.example.MainTest
[ERROR] com.wengnerits.modules.example.MainTest.mainMessageTest  Time elapsed: 0.003 s  <<< ERROR!
java.lang.reflect.InaccessibleObjectException: Unable to make void com.wengnerits.modules.example.MainTest.mainMessageTest() accessible: module main does not "opens com.wengnerits.modules.example" to unnamed module @c86b9e3
…
[ERROR] Errors: 
[ERROR]   MainTest.mainMessageTest » InaccessibleObject Unable to make void com.wengneri...
[ERROR]   SimpleProducerTests.listProducer » InaccessibleObject Unable to make com.wengn...
[INFO] 
[ERROR] Tests run: 3, Failures: 0, Errors: 2, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for java-module-junit5-parent 1.0-SNAPSHOT:
[INFO] 
[INFO] java-modules-junit5-parent .......................... SUCCESS [  0.172 s]
[INFO] java-modules-junit5: main ........................... FAILURE [  2.350 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE 

The result is not a surprise.  The junit-5 engine ( @c86b9e3 ) does not have a guaranteed  to use the reflection inside the package and sub-packages. The not public “Access Modifier” are causing the errors to the JUnit engine (@c86b9e3). The only tests that has been properly executed resides inside the public class MainTest, annotated method mainValidTest.

The all other tests have cased an error, example:

 
java.lang.reflect.InaccessibleObjectException: Unable to make void com.wengnerits.modules.example.MainTest.mainMessageTest() accessible: module main does not "opens com.wengnerits.modules.example" to unnamed module @c86b9e3
 

To solve the issue by using the “Java Moduledirective, it is required a change the “main” java module descriptor file. The example project approach has been described above:

 
open module main {
    exports com.wengnerits.modules.example;
}
 

Now the “mvn install” command can be executed  again with the desired result:

…
Reactor Summary for java-modules-junit5-parent 1.0-SNAPSHOT:
[INFO] 
[INFO] java-modules-junit5-parent .......................... SUCCESS [  0.170 s]
[INFO] java-modules-junit5: main ........................... SUCCESS [  2.663 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
…