Table of Contents
To declare modules for Java 9’s module system we need to create a file module-info.java
– a so called module declaration. Amongst other things, it declares dependencies on other modules. If the project uses a build tool, though, Maven for example, it already manages dependencies. Keeping two files in sync surely seems redundant and error-prone, so the question arises: “Can’t Maven generate the module-info file for me?” Unfortunately, the answer is “No”, and here is why.
(To get the most out of this article, you should be familiar with the module system’s basics, particularly how it manages dependencies. If you want to read up on that, check out this hands-on guide.)
The Motivation
So everyone wonders, can Maven generate the module declaration? The reason is obvious: If I add a dependency to a POM, it is very likely a requirement, too. Or the other way around: If I add a requirement to the declaration, probably a matching dependency must be added to the POM as well. Sounds like something which could be automated.
Let’s start with the attempt to go from requirement back to a dependency: This is not possible because of the lack of information. A module name cannot be transformed to a Maven coordinate, information like the groupId and artifactId is missing. And also: Which version to choose? The module declaration is not interested in which version of an artifact is on the path, only that it is available.
On the other hand, to go from dependency-file to module name is possible. However, that’s not enough to generate all the elements of the module declaration.
Separation of Concerns
When talking about this topic there are three entities in play: Maven’s POM, the module system’s module-info file, and Maven plugins that are involved in building class and module paths. The three together make it possible to work with modules, but they all have their own responsibilities.
POM Dependencies
The task of a dependency is (1) to have a unique reference to a file based on the coordinate and (2) to specify when to use it. The when to use it part is controlled by the scope
and is basically any combination of compile
, test
, and runtime
.
The coordinate is a combination of at least the groupId
, artifactId
, version
and file-extension, which is derived from the type
. Optionally a classifier
could be added as well. Based in this information it is possible to refer to a file in the local repository. It’ll look like the following, where every dot in the groupId is replaced with a slash:
${localRepo}/${groupId}/${artifactId}/${version}/
${artifactId}-${version}[-${classifier}].${ext}
As you can see, you can refer to any file: a text-file, an image, an executable. Within the context of the dependency it doesn’t matter. To add to this, the dependency has no idea about the content of the file. For instance in case of a JAR: was it built for Java 8 or Java 1.4, which could make a big difference in case your project has to be compatible with a rather old Java version.
Module Declaration
The module declaration file is built up with five declarations:
- requires
- The module(s) that must be available to compile or run this application.
- exports
- The package(s) whose types are visible to a few or all modules.
- opens
- The package(s) whose types are [accessible via reflection](https://www.sitepoint.com/reflection-vs-encapsulation-in-the-java-module-system/) to a few or all modules.
- uses
- The service interface(s) that this module may discover.
- provides
- The implementation(s) provided for a certain service interface.
From these declarations the requires
clauses are closely related to the dependencies of the Maven project. If the JDK/JRE together with the dependency-files provided by Maven doesn’t cover these requirements, the project simply won’t compile or run. Consider it as a quality rule one must obey.
Maven Plugins
Every plugin (or actually plugin-goal) can specify the resolution scope for dependencies and get access to these file. For instance the compile-goal of the maven-compiler-plugin states that it uses compile-time dependency resolution. It is up to this plugin to construct the correct arguments for the Java compiler based on the dependency-files provided by Maven and the configuration of the plugin.
Class Paths and Module Paths
Up until Java 8 it was quite simple: All JARs need to be added to the class path. But with Jigsaw, JARs can end up either on the module path or the class path. It all depends on whether a JAR is required by another module or not. It is important to realize that we have to get this right 100% of the time because if an artifact ends up on the wrong path, the module system will reject the configuration and compilation or launch will fail.
Let’s assume we’ve written a module declaration for our project. Where do we have to specify if a JAR is a required module or not? In other words, does it belong on the class path or the module path? One of the obvious locations is the dependency in the POM. However, there are several reasons why this won’t work.
Not a Concern
First of all, it is not the concern of the dependency. A dependency is about a reference to a file and when to use it (compile time? run time?) not how.
No Place in the POM
Then, from all the elements of a dependency there’s only one which might be used to control a more fine-grained usage of the JAR: the scope
. However, the POM’s modelVersion 4.0.0 has a strict set of scopes. Although the POM is filled with the build instructions for Maven, once installed or deployed it is also a meta-file with dependency information for other build tools and IDEs, so new scopes might confuse or break such products.
For the same reason the introduction of a new XML-element for dependencies would be problematic; it would conflict with the XSD and other tools are not prepared for it yet. So there’s no space for modular information in the dependency declaration of the POM and for the short term one shouldn’t expect a new POM definition just for Jigsaw.
So how about configuring it with the maven-compiler-plugin?
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<!-- <version>x.y.z</version> -->
<configuration>
<!-- example -->
<modulePath>
<!-- by dependency? -->
<dependency>com.foo.bar:library</dependency>
<!-- by module? -->
<module>library</module>
</modulePath>
</configuration>
</plugin>
But what about the transitive dependencies, in this case the dependencies of library? Those dependencies need to be divided over both the paths as well. Do you need to configure that?
Alternatively we might want to resolve the build information of a dependency to infer where to place its dependencies but that is not feasible as this kind of information isn’t always there. Even when such a JAR was built with Maven, trying to reverse engineer the content from the JAR to the effective plugin configurations is often not possible (think of the effect of multiple execution blocks).
Reading the Module Descriptor
So if the POM is not the way to go, where else could we get the information from? Better focus on the files inside the JAR as-is, for instance a module-info.class
… (the compiled module declaration, called the module descriptor).
So the key file seems to be the module-info
file as it exactly specifies the requirements. Once tools like ASM can read the module descriptor, the maven-compiler-plugin can extract that information and decide per dependency if it matches a required module. For our project there should be a module-info.java
so Maven knows where to place our dependencies. For that it needs to be analyzed before compilation, which is already possible with QDox.
So with all module-info.class
-files inside JARs and the module-info.java
-file of our project it is possible to decide where every JAR belongs; either the class path or the module path. This makes the module declarations and descriptors an ingredient for a successful build. But the question remains, could it not also be a result?
The Module Declaration Generator
When analyzing all the module descriptors used in this project we see that it should be possible to divide all JARs over the module path and the class path. But is it possible to generate the module declaration for our project?
For the requirements in src/main/java/module-info.java
we can get quite far. We could say that all dependencies with scopes compile
or provided
are requirements, test
of course not, but for runtime
(which means at run time only) it depends. But there are also requirements, which are not mapped to a JAR. Instead they refer to a java or jdk module (e.g. java.sql
, java.xml
or jdk.packager
). These must be configured for a generator or the code needs to be analyzed up front.
A requirement can also have additional modifiers. With a requires static
clause you can specify that a module is mandatory at compile but optional at run time. In Maven you would mark such a dependency as optional
. With the transitive
modifier you specify that projects using this module don’t have to add the transitive module in their own module declaration file. So transitive
has zero effect on this project, it only helps other projects using this one as a dependency. Such information can only be provided as configuration.
Even if the POM definition would be redesigned to support this kind of dependency metadata in order to be transformed to the requirements in the module declaration file, there are much more declarations, which are much harder to generate. It already starts with the module itself: What is its name, is it open or not?
And how about the exported and open packages? It might look as if they could be inferred by code analyses. Analyzing java files as sources at this detail is not yet possible, though. Analyzing class files as binaries means double compiling, first with only the class path to get all classes for analysis, next with both the module path and class path, being the actual compilation. There is no feasible solution.
In the end every line in the module declaration is a choice by the developer. Does it make sense to add configuration to the POM, which already looks a lot like the intended module declaration file?
The closest we could get is with a template like the following. Let’s use Velocity and call the template module-info.java.vm
.
open module M.N
{
#set ( $requiredModules = ... ) ## these must somehow be available in the context
## requires {RequiresModifier} ModuleName ;
#set( $transitiveModules = ["org.acme.M1", "org.acme.M2"] )
#foreach( $requiredModule in ${requiredModules} )
#if( $transitiveModules.contains( $requiredModule.name ) ) transitive #end #if( $requiredModule.optional ) static #end ${requiredModule.name} ;
#end
requires java.sql;
requires java.xml;
requires jdk.packager;
## exports PackageName [to ModuleName {, ModuleName}] ;
exports com.acme.product;
exports com.acme.service;
## opens PackageName [to ModuleName {, ModuleName}] ;
## uses TypeName ;
## provides TypeName with TypeName {, TypeName} ;
}
Transforming this to the module-info.java
requires probably a separate templating maven-plugin, because this goes beyond the compilation task of the maven-compiler-plugin and the resource copying with optional filtering task of the maven-resource-plugin. It’s up to the developer if such template with some scripting syntax is better to read and maintain compared to a plain old (new) module declaration file.
Some might have heard about jdeps, a tool available since JDK8 which can analyze dependencies. It also has an option to generate the module declaration file, but not in the way we want it. Jdeps only uses compiled classes, so it must be used after the compile
phase. This features was introduced for developers to have a module-info.java
-file to start with, but once generated it is up to the developer to adjust and maintain it.
Conclusion
If there were a module declaration generator, it still required quite a lot of configuration to get the resulting file just right. This would not be less work than just writing the file directly. All together, writing and maintaining the module declaration yourself gives the guarantee that it will always be as you would expect.
Of course some small open source projects will pop up and try to generate the module declaration anyway, but it’ll only work for a subset of projects, which means we cannot expose it for Maven.
I am more hopeful that IDEs will resolve this for you. For instance if you add a dependency to the POM, you could get the option to add it as requirement to the module declaration as well. Or that they provide a wizard showing all dependencies and packages from your project, giving you the option what to add to the module declaration file.
However that part of the story plays out, though, I’m here to tell you that for the time being Maven won’t be writing your module declaration for you.
Frequently Asked Questions (FAQs) about Maven Module Declaration
What is a Maven module declaration and why is it important?
A Maven module declaration is a key component of a Maven project. It is defined in the pom.xml file and it specifies the modules that make up a multi-module project. Each module is a project on its own, but they are grouped together through the module declaration. This is important because it allows for the management and building of multiple related projects at once. It also promotes code reuse and separation of concerns, making the overall project easier to manage and maintain.
Why am I getting a ‘cannot generate module declaration’ error in Maven?
The ‘cannot generate module declaration’ error in Maven typically occurs when the module name in the pom.xml file does not match the actual module name in your project. This could be due to a typo, a case sensitivity issue, or the module simply not existing. It’s important to double-check your module names and ensure they match exactly.
How can I resolve the ‘cannot generate module declaration’ error in Maven?
To resolve this error, you need to ensure that the module name in your pom.xml file matches exactly with the actual module name in your project. If the names do not match, Maven will not be able to generate the module declaration. If the names do match and you’re still encountering the error, it may be due to a problem with your Maven configuration or a bug in Maven itself.
Can Maven generate the module declaration automatically?
No, Maven does not automatically generate module declarations. You need to manually define your modules in the pom.xml file. However, there are tools and plugins available that can help automate this process, such as the Maven Archetype plugin.
What is the structure of a Maven module declaration?
A Maven module declaration is structured as an XML element in the pom.xml file. It starts with the
What is the role of the pom.xml file in Maven?
The pom.xml file is the Project Object Model in Maven. It contains information about the project and configuration details used by Maven to build the project. It includes the project dependencies, the plugins or goals that can be executed, the build profiles, and so on. It’s also where you define your modules in a multi-module project.
How does Maven handle dependencies in a multi-module project?
In a multi-module project, Maven handles dependencies in a hierarchical manner. The parent project’s pom.xml file can define common dependencies for all its modules. Each module can also have its own dependencies defined in its own pom.xml file. Maven will automatically resolve these dependencies when building the project.
Can I use Maven with JavaFX?
Yes, you can use Maven with JavaFX. There are Maven plugins available, such as the JavaFX Maven plugin, that can help manage and build your JavaFX projects. However, you may encounter issues with module declarations, as JavaFX uses its own module system.
What is the difference between a Maven project and a Maven module?
A Maven project is a single entity that can be built using Maven. It contains a pom.xml file that defines the project and its dependencies. A Maven module, on the other hand, is a part of a multi-module project. Each module is a project on its own, but they are grouped together through the module declaration in the parent project’s pom.xml file.
How can I ensure that my Maven project builds successfully?
To ensure that your Maven project builds successfully, you need to make sure that your pom.xml file is correctly configured. This includes correctly defining your project dependencies, plugins, and, if it’s a multi-module project, your module declarations. You should also ensure that your project follows the correct directory structure as expected by Maven.
Robert Scholte is the current chairman of the Apache Maven project and has been a member of this project for over five years and belongs to the group of most active committers. The last couple of years he has been busy preparing Maven to support Java 9, which means that he has done most of the implementations required to adopt all the new features. Robert has spoken on several conferences such as JavaOne and Devoxx Belgium about Java 9 and the impact on Maven projects. This year he has joined the JSR 376 Expert Group, which validates the specifications of the Java Platform Module System.