Like many people I was still a bit behind on moving to Java 9+, especially on using the new Java Platform Module System. I maintain only a small library and I didn’t want to break any products using my library for no reason.

I finally took the time to dive into it and I wrote this blog post to help other library maintainers who are struggling with the question: How can I make my library Jigsaw/Java Module System compliant without hassles?

For this example I am assuming an archetypical Maven setup (with src/main and src/test folders, tests to be run but not packaged in your artifact). There are basically four steps involved in this process:

  1. Make sure your packages are in order
  2. Choose and set an automatic module name
  3. Move to Java 9+
  4. Become a full module

The first step is more of a prerequisites check, as most everything should already be in order if you designed your library well, but it never hurts to double check and clean up some loose ends.
You can do the first two steps on Java 8 but you need Java 9+ to use a module descriptor (module-info) and become a full module.

Make sure your packages are in order

There are some broadly accepted best practices regarding how to structure your packages and with the introduction of the Java Module System these are becoming even more important. Java will not allow modules which break these rules and this can become an issue for anyone trying to use your library in a jigsaw-compliant way, even if your library is not, itself, jigsaw-compliant.

The most important of these rules is to not split packages. A package should exist in only one module and there should be no other module in the world which contains classes in the same package.

This also means that your library can’t add to a package from another library, let alone a core package (so no more adding annotations to javax.annotation).

The easiest way to ensure this is to create one ‘super-package’ for each module. Ie, if your domain is example.com, and your module is named ‘mario’, then simply ensure every package name in your module starts with com.example.mario.

If another of your modules is named ‘luigi’, then all package names in that module should start with com.example.luigi.

This way, there is no chance of you splitting packages between your own modules and (assuming the domain you use is yours) there is also no chance of you splitting packages with someone else’s module.

As an added bonus, this also makes it easy to pick your globally-unique module name: simply use this same ‘super-package’ as your module name. So ‘mario’ would be named com.example.mario, etc.

Stephen Colebourne wrote a very good blog post about naming your modules which goes into greater detail on this. There’s also a lot of information in the Guava issue about this, though theirs is a more special case than most.

Choose and set an automatic module name

Setting an automatic module name is the first step towards being JPMS compliant. If your module has an official name, other modules can declare their dependency on the contents of your module without fear that the name will suddenly change.

Similarly, if your module declares an API, other artifacts can deliver their own version of that same API by using the same module name and your artifacts would be interchangeable as long as the module name wouldn’t change.

Once you’ve picked a module name, actually setting it is easy. You can add an Automatic-Module-Name entry to the MANIFEST.MF of your jar. If you use maven, that is as simple as configuring the maven-jar-plugin as follows:

<build>
  <pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.1.2</version>
        <configuration>
          <archive>
            <manifestEntries>
              <Automatic-Module-Name>com.example.mario</Automatic-Module-Name>
            </manifestEntries>
          </archive>
        </configuration>
      </plugin>
    </plugins>
  </pluginManagement>
</build>

You can do this even while your source is still being compiled on Java 8. This way, your library is ready to be used on JDK9+ without having to change your source compliance level.

Move to Java 9

Before you can go further and create a module-info you need to move to Java 9 as Java 8 is not compatible with module descriptors. This might also be a good time to check you are not depending on jdk-internal classes.

Simply compiling against Java 9+ will help you find use of jdk internals in your own code. Make sure you use the new release-flag as well, especially if you’re compiling on a JDK with a different major version. This will ensure the compiler uses the correct set of available JDK internal dependencies.

<build>
  <pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <configuration>
          <source>9</source>
          <target>9</target>
          <release>9</release>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
  </pluginManagement>
</build>

You can also use jdeps to check your own project as well as all its dependencies with the --jdk-internals flag. Nicolai Parlog wrote about it in his excellent blogpost on migrating to Java 11. He also wrote a maven plugin that builds upon jdeps to fail the build if it detects dependencies on JDK internals.

Become a full module

Once your library is Java 9+ you have done all that you can. If your library has no external dependencies, you can write a module-info.java and include it by placing it directly inside the src/main/java folder, at the top level.

module com.example.mario
{
  exports com.example.mario.api;
  exports com.example.mario.api.pipe; 
}

This module descriptor declares the artifact to contain the module com.example.mario, which exposes all public classes in the com.example.mario.api and com.example.mario.api.pipe packages.

If your code depends upon classes from some other module, say com.example.princess.peach, you’d have to declare your dependency upon the public API of this module.

module com.example.mario
{
  exports com.example.mario.api;
  exports com.example.mario.api.pipe;

  requires com.example.princess.peach;
}

If you expose classes from a module as part of your own public API, you need to declare that module transitively. For example, say you have the following class:

package com.example.mario.api.pipe;

import com.example.princess.peach.jumps.FloatingJump;

public class Pipe
{
  //...
  public void tryFloat(FloatingJump jumpDescriptor)
  {
    //...
  }
}

Your API now includes a transitive dependency on the com.example.princess.peach.jumps.FloatingJump class. If other projects want to depend upon your module, they have to have this class avalable to them.

The easiest way to accomplish this is to declare this as a transitive dependency of your module. This way, those classes will be accessible to users of your library without them having to declare those dependencies themselves (and keeping track of what dependencies your library has).

module com.example.mario
{
  exports com.example.mario.api;
  exports com.example.mario.api.pipe;

  requires transitive com.example.princess.peach;
}

For another example, I had to declare a transitive dependency on com.fasterxml.jackson.databind because as part of my public API I exposed a class ObjectMapperFactory which, as the name implies, had a couple of factory methods that returned pre-configured ObjectMapper‘s.

If all of your dependencies are proper modules listing your (transitive) dependencies will be fairly easily. You can get their module names directly from their module descriptor and you can be reasonably sure they will have declared the transitive dependencies of their module as such.

However, if some of your dependencies are not proper modules it gets a bit more complex, depending on how far the other library’s maintainers have gone towards JPMS compliancy.

If they have chosen and set an Automatic-Module-Name, you should be able to depend upon that not changing between versions and use that module name as you declare your dependency. However, they have no module descriptor and therefore have not declared their transitive dependencies as such. This means that you’d have to find out what, if any, their transitive dependencies are and declare them as dependencies of your own module, keeping in mind these dependencies can change.

It’s up to you if you are willing to take on the extra maintenance load from doing this. In general, the more dependencies they have and the deeper their dependency tree, the more complex this will get.

If any of your (transitive) dependencies does not even have declared an Automatic-Module-Name entry I would not even try to construct a module descriptor and just leave it at the Automatic-Module-Name for your own library as well. This unfortunately means that your library will not be fully JPMS compliant until all your dependencies have at least declared an Automatic-Module-Name.

This dependency on external libraries is one of the bigger problems with the adoption of JPMS and, through that, Java 9. Be proud that you have taken the first step towards becoming a full module by thinking about and declaring your own Automatic-Module-Name and in doing so have helped other authors who depend upon your module to become JPMS compliant.

Module dependencies vs maven dependencies

Note, that dependencies in the module descriptor have only to do with accessibility (or visibility). If you want to have the classes from a certain module available at all you still need to declare a dependency on the artifact containing this module in your pom.xml

In other words, maven dependencies are about what artifacts you want to have available. Module dependencies are about what classes (packages) you need to have accessible.

A module dependency can be filled by different artifacts. For example, both the javax.servlet-api and the jboss-servlet-api artifacts contain the same set of classes and should therefore have the same module name, something like javax.servlet

You can think of this as different artifacts being able to implement the same module, similar to how different classes can implement the same interface.

Leave a Reply

Your email address will not be published. Required fields are marked *