In the previous guide, Getting Started with the Java Platform Module System Part 2, I covered examples for working with modules and how to use ServiceLoader
with modules. In the final guide of this series, I'll talk about how Maven works with modules and some final thoughts.
Here's a commonly asked question: Is there an overlap between Maven and JPMS?
The answer is no: they complement each other.
While modularization is more about encapsulation and visibility (i.e. deciding which packages can be seen outside a module/JAR), Maven is about dependency management and compiling code into artifacts. These two things work at different levels.
On the topic of dependency management, every Maven artifact has three parts:
The JPMS does not know about versions and, depending on naming conventions, the name of a Java module can be the same as the artifact ID, the union of the group and artifact ID, or something completely different.
The bottom line is that, because of this, we cannot directly associate a Java module with a Maven POM dependency/artifact. This means:
module-info.java
file.In a modular application, adding a dependency involves two steps now:
module-info.java
file of the project.For example, let's assume our project consists of two modules, one for the API and one for the GUI, which depends on the API module.
We can structure our project with Maven modules. At the high-level, the concept is basically the same. This is what the parent pom.xml
would look like:
1<project xmlns="http://maven.apache.org/POM/4.0.0"
2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
4 http://maven.apache.org/maven-v4_0_0.xsd">
5 <modelVersion>4.0.0</modelVersion>
6
7 <groupId>org.example</groupId>
8 <artifactId>my-parent</artifactId>
9 <packaging>pom</packaging>
10 <version>1.0</version>
11 <name>Parent project</name>
12
13 <modules>
14 <module>api</module>
15 <module>gui</module>
16 </modules>
17
18 <build>
19 <pluginManagement>
20 <plugins>
21 <plugin>
22 <groupId>org.apache.maven.plugins</groupId>
23 <artifactId>maven-compiler-plugin</artifactId>
24 <version>3.7.0</version>
25 <configuration>
26 <source>9</source>
27 <target>9</target>
28 </configuration>
29 </plugin>
30 </plugins>
31 </pluginManagement>
32 </build>
33
34 <!--
35 ...
36 -->
37
38</project>
Nothing is different from what we are used to, except maybe for the maven-compiler-plugin
configuration. Make sure to use at least version 3.6.1.
The directory structure of the project can be similar to the following:
1|─ api
2| |─ pom.xml
3| |─ src
4| |─ main
5| |─ java
6| |─ module-info.java
7| |─ my
8| |─ example
9| |─ api
10|─ gui
11| |─ pom.xml
12| |─ src
13| |─ main
14| |─ java
15| |─ module-info.java
16| |─ my
17| |─ example
18| |─ gui
19|- pom.xml
For the API module, the pom.xml
will look like this:
1<project xmlns="http://maven.apache.org/POM/4.0.0"
2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
4 http://maven.apache.org/maven-v4_0_0.xsd">
5 <modelVersion>4.0.0</modelVersion>
6
7 <parent>
8 <groupId>org.example</groupId>
9 <artifactId>my-parent</artifactId>
10 <version>1.0</version>
11 </parent>
12 <artifactId>api</artifactId>
13 <packaging>jar</packaging>
14 <name>API project</name>
15
16 <dependencies>
17 <!--
18 ...
19 -->
20 </dependencies>
21
22 <!--
23 ...
24 -->
25
26</project>
This would be the content of the module-info.java
file:
1module my.company.api {
2 exports my.company.api;
3 // ...
4}
Now, in the GUI module, we have to add the dependecy to the API project to its pom.xml
:
1<project xmlns="http://maven.apache.org/POM/4.0.0"
2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
4 http://maven.apache.org/maven-v4_0_0.xsd">
5 <modelVersion>4.0.0</modelVersion>
6
7 <parent>
8 <groupId>org.example</groupId>
9 <artifactId>my-parent</artifactId>
10 <version>1.0</version>
11 </parent>
12 <artifactId>gui</artifactId>
13 <packaging>jar</packaging>
14 <name>GUI project</name>
15
16 <dependencies>
17 <dependency>
18 <groupId>org.example</groupId>
19 <artifactId>api</artifactId>
20 <version>1.0</version>
21 </dependency>
22 <!--
23 ...
24 -->
25 </dependencies>
26
27 <!--
28 ...
29 -->
30
31</project>
As well as in module-info.java
:
1module my.company.webapp {
2 requires my.company.api;
3 // ...
4}
This way, when it's time to build the project (for example, with mvn package
), the Maven compiler plug-in will set up the module path so that the Java compiler can work correctly.
The module system has been introduced to Java to promote stronger encapsulation and better design, while making Java a more flexible and future-proof language. There are features like incubator modules that will bring new APIs to us while they progress towards either finalization or removal in a future release, giving us the chance to provide input. This also fits pretty well with the proposed fast release cycle for Java.
In this guide, you have learned the basics of the JPMS; including the benefits of modules, how they work, and how they integrate with a tool like Maven.
Of course, there are several more things to learn, like custom runtime images, how to migrate classpath projects to modules, or how to unit test a modular project.
The best book I have read about JPMS is Java 9 Modularity by Sander Mak and Paul Bakker. I totally recommended it. Also, The Java Module System by Nicolai Parlog is a book that, at the time of writing this guide, is currently in progress yet looks very promising. Definitely look to these resources if you find yourself using Java 9 and JPMS in the future.
You can find the code of the sample JavaFX with ServiceLoader and Maven apps on this GitHub repository.