javamavenjlinkmoditect

Using Moditect to add module-info to existing maven dependencies


I am using JavaFX and have some dependencies that are not automatic modules. When trying to run mvn clean javafx:jlink I get the following error:

Error: automatic module cannot be used with jlink: org.apache.commons.io from [...]`

This also errored for org.apache.commons.commons-lang3 and commons-codec.commons-codec. Which error I was shown was random.

So I looked into using jdeps to generate a module info for the dependency, e.g.

jdeps --generate-module-info tmp .../.m2/repository/commons-io/commons-io/2.12.0/commons-io-2.12.0.jar

Then I used Moditect to add the module-info to the existing jar:

<plugin>
    <groupId>org.moditect</groupId>
    <artifactId>moditect-maven-plugin</artifactId>
    <version>1.0.0.Final</version>
    <executions>
        <execution>
            <id>add-module-infos</id>
            <phase>generate-resources</phase>
            <goals>
                <goal>add-module-info</goal>
            </goals>
            <configuration>
                <outputDirectory>${project.build.directory}/modules</outputDirectory>
                <modules>
                    <module>
                        <artifact>
                            <groupId>commons-io</groupId>
                            <artifactId>commons-io</artifactId>
                            <version>2.12.0</version>
                        </artifact>
                        <moduleInfoSource>
                            module org.apache.commons.io {
                                exports org.apache.commons.io;
                                exports org.apache.commons.io.build;
                                exports org.apache.commons.io.charset;
                                exports org.apache.commons.io.comparator;
                                exports org.apache.commons.io.file;
                                exports org.apache.commons.io.file.attribute;
                                exports org.apache.commons.io.file.spi;
                                exports org.apache.commons.io.filefilter;
                                exports org.apache.commons.io.function;
                                exports org.apache.commons.io.input;
                                exports org.apache.commons.io.input.buffer;
                                exports org.apache.commons.io.monitor;
                                exports org.apache.commons.io.output;
                                exports org.apache.commons.io.serialization;
                            }
                        </moduleInfoSource>
                    </module>
                    <module>
                        <artifact>
                            <groupId>org.apache.commons</groupId>
                            <artifactId>commons-lang3</artifactId>
                            <version>3.12.0</version>
                        </artifact>
                        <moduleInfoSource>
                            module org.apache.commons.lang3 {
                                requires transitive java.desktop;

                                exports org.apache.commons.lang3;
                                exports org.apache.commons.lang3.arch;
                                exports org.apache.commons.lang3.builder;
                                exports org.apache.commons.lang3.compare;
                                exports org.apache.commons.lang3.concurrent;
                                exports org.apache.commons.lang3.concurrent.locks;
                                exports org.apache.commons.lang3.event;
                                exports org.apache.commons.lang3.exception;
                                exports org.apache.commons.lang3.function;
                                exports org.apache.commons.lang3.math;
                                exports org.apache.commons.lang3.mutable;
                                exports org.apache.commons.lang3.reflect;
                                exports org.apache.commons.lang3.stream;
                                exports org.apache.commons.lang3.text;
                                exports org.apache.commons.lang3.text.translate;
                                exports org.apache.commons.lang3.time;
                                exports org.apache.commons.lang3.tuple;
                            }
                        </moduleInfoSource>
                    </module>
                    <module>
                        <artifact>
                            <groupId>commons-codec</groupId>
                            <artifactId>commons-codec</artifactId>
                            <version>1.15</version>
                        </artifact>
                        <moduleInfoSource>
                            module org.apache.commons.codec {
                                exports org.apache.commons.codec;
                                exports org.apache.commons.codec.binary;
                                exports org.apache.commons.codec.cli;
                                exports org.apache.commons.codec.digest;
                                exports org.apache.commons.codec.language;
                                exports org.apache.commons.codec.language.bm;
                                exports org.apache.commons.codec.net;
                            }
                        </moduleInfoSource>
                    </module>
                </modules>
            </configuration>
        </execution>
    </executions>
</plugin>

But I am still getting the same errors.

My module-info:

module uk.co.conoregan.showrenamer {
    requires info.movito.themoviedbapi;
    requires java.prefs;
    requires javafx.fxml;
    requires javafx.controls;
    requires org.apache.commons.io;
    requires org.slf4j;

    opens uk.co.conoregan.showrenamer to javafx.fxml, javafx.graphics;
    opens uk.co.conoregan.showrenamer.controller to javafx.fxml;

    exports uk.co.conoregan.showrenamer;
    exports uk.co.conoregan.showrenamer.api;
    exports uk.co.conoregan.showrenamer.config.preference;
    exports uk.co.conoregan.showrenamer.config.property;
    exports uk.co.conoregan.showrenamer.controller;
    exports uk.co.conoregan.showrenamer.suggestion;
    exports uk.co.conoregan.showrenamer.util;
}

My full pom.xml:

<?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>

    <name>ShowRenamer</name>
    <groupId>uk.co.conoregan.show-renamer</groupId>
    <artifactId>show-renamer</artifactId>
    <version>1.4.0</version>

    <properties>
        <maven.compiler.java.version>17</maven.compiler.java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <junit.version>5.9.3</junit.version>
        <javafx.version>20.0.1</javafx.version>
        <slf4j.version>2.0.7</slf4j.version>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>${slf4j.version}</version>
            <scope>runtime</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>5.3.1</version>
            <scope>test</scope>
        </dependency>

        <!-- JavaFX -->
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>${javafx.version}</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>${javafx.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.12.0</version>
        </dependency>

        <!-- https://search.maven.org/artifact/com.github.holgerbrandl/themoviedbapi -->
        <dependency>
            <groupId>com.github.holgerbrandl</groupId>
            <artifactId>themoviedbapi</artifactId>
            <version>1.15</version>
        </dependency>
    </dependencies>

    <build>
        <testResources>
            <testResource>
                <directory>${project.basedir}/src/test/resources</directory>
            </testResource>
        </testResources>

        <plugins>
            <plugin>
                <groupId>org.moditect</groupId>
                <artifactId>moditect-maven-plugin</artifactId>
                <version>1.0.0.Final</version>
                <executions>
                    <execution>
                        <id>add-module-infos</id>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>add-module-info</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/modules</outputDirectory>
                            <modules>
                                <module>
                                    <artifact>
                                        <groupId>commons-io</groupId>
                                        <artifactId>commons-io</artifactId>
                                        <version>2.12.0</version>
                                    </artifact>
                                    <moduleInfoSource>
                                        module org.apache.commons.io {
                                            exports org.apache.commons.io;
                                            exports org.apache.commons.io.build;
                                            exports org.apache.commons.io.charset;
                                            exports org.apache.commons.io.comparator;
                                            exports org.apache.commons.io.file;
                                            exports org.apache.commons.io.file.attribute;
                                            exports org.apache.commons.io.file.spi;
                                            exports org.apache.commons.io.filefilter;
                                            exports org.apache.commons.io.function;
                                            exports org.apache.commons.io.input;
                                            exports org.apache.commons.io.input.buffer;
                                            exports org.apache.commons.io.monitor;
                                            exports org.apache.commons.io.output;
                                            exports org.apache.commons.io.serialization;
                                        }
                                    </moduleInfoSource>
                                </module>
                                <module>
                                    <artifact>
                                        <groupId>org.apache.commons</groupId>
                                        <artifactId>commons-lang3</artifactId>
                                        <version>3.12.0</version>
                                    </artifact>
                                    <moduleInfoSource>
                                        module org.apache.commons.lang3 {
                                            requires transitive java.desktop;

                                            exports org.apache.commons.lang3;
                                            exports org.apache.commons.lang3.arch;
                                            exports org.apache.commons.lang3.builder;
                                            exports org.apache.commons.lang3.compare;
                                            exports org.apache.commons.lang3.concurrent;
                                            exports org.apache.commons.lang3.concurrent.locks;
                                            exports org.apache.commons.lang3.event;
                                            exports org.apache.commons.lang3.exception;
                                            exports org.apache.commons.lang3.function;
                                            exports org.apache.commons.lang3.math;
                                            exports org.apache.commons.lang3.mutable;
                                            exports org.apache.commons.lang3.reflect;
                                            exports org.apache.commons.lang3.stream;
                                            exports org.apache.commons.lang3.text;
                                            exports org.apache.commons.lang3.text.translate;
                                            exports org.apache.commons.lang3.time;
                                            exports org.apache.commons.lang3.tuple;
                                        }
                                    </moduleInfoSource>
                                </module>
                                <module>
                                    <artifact>
                                        <groupId>commons-codec</groupId>
                                        <artifactId>commons-codec</artifactId>
                                        <version>1.15</version>
                                    </artifact>
                                    <moduleInfoSource>
                                        module org.apache.commons.codec {
                                            exports org.apache.commons.codec;
                                            exports org.apache.commons.codec.binary;
                                            exports org.apache.commons.codec.cli;
                                            exports org.apache.commons.codec.digest;
                                            exports org.apache.commons.codec.language;
                                            exports org.apache.commons.codec.language.bm;
                                            exports org.apache.commons.codec.net;
                                        }
                                    </moduleInfoSource>
                                </module>
                            </modules>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.1.0</version>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>${maven.compiler.java.version}</source>
                    <target>${maven.compiler.java.version}</target>
                    <release>${maven.compiler.java.version}</release>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.8</version>
                <executions>
                    <execution>
                        <!-- Default configuration for running with: mvn clean javafx:run -->
                        <id>default-cli</id>
                        <configuration>
                            <mainClass>uk.co.conoregan.showrenamer/uk.co.conoregan.showrenamer.ShowRenamerApplication</mainClass>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

I also ran mvn dependency:tree to generate the following:

[INFO] uk.co.conoregan.show-renamer:show-renamer:jar:1.4.0
[INFO] +- org.slf4j:slf4j-api:jar:2.0.7:compile
[INFO] +- org.slf4j:slf4j-simple:jar:2.0.7:runtime
[INFO] +- org.junit.jupiter:junit-jupiter-api:jar:5.9.3:test
[INFO] |  +- org.opentest4j:opentest4j:jar:1.2.0:test
[INFO] |  +- org.junit.platform:junit-platform-commons:jar:1.9.3:test
[INFO] |  \- org.apiguardian:apiguardian-api:jar:1.1.2:test
[INFO] +- org.junit.jupiter:junit-jupiter-engine:jar:5.9.3:test
[INFO] |  \- org.junit.platform:junit-platform-engine:jar:1.9.3:test
[INFO] +- org.mockito:mockito-core:jar:5.3.1:test
[INFO] |  +- net.bytebuddy:byte-buddy:jar:1.14.4:test
[INFO] |  +- net.bytebuddy:byte-buddy-agent:jar:1.14.4:test
[INFO] |  \- org.objenesis:objenesis:jar:3.3:test
[INFO] +- org.openjfx:javafx-controls:jar:20.0.1:compile
[INFO] |  +- org.openjfx:javafx-controls:jar:win:20.0.1:compile
[INFO] |  \- org.openjfx:javafx-graphics:jar:20.0.1:compile
[INFO] |     +- org.openjfx:javafx-graphics:jar:win:20.0.1:compile
[INFO] |     \- org.openjfx:javafx-base:jar:20.0.1:compile
[INFO] |        \- org.openjfx:javafx-base:jar:win:20.0.1:compile
[INFO] +- org.openjfx:javafx-fxml:jar:20.0.1:compile
[INFO] |  \- org.openjfx:javafx-fxml:jar:win:20.0.1:compile
[INFO] +- commons-io:commons-io:jar:2.12.0:compile
[INFO] \- com.github.holgerbrandl:themoviedbapi:jar:1.15:compile
[INFO]    +- org.slf4j:jcl-over-slf4j:jar:2.0.7:runtime
[INFO]    +- com.fasterxml.jackson.core:jackson-annotations:jar:2.15.0:runtime
[INFO]    +- com.fasterxml.jackson.core:jackson-core:jar:2.15.0:runtime
[INFO]    +- com.fasterxml.jackson.core:jackson-databind:jar:2.15.0:runtime
[INFO]    +- com.google.guava:guava:jar:31.1-jre:runtime
[INFO]    |  +- com.google.guava:failureaccess:jar:1.0.1:runtime
[INFO]    |  +- com.google.guava:listenablefuture:jar:9999.0-empty-to-avoid-conflict-with-guava:runtime
[INFO]    |  +- com.google.code.findbugs:jsr305:jar:3.0.2:runtime
[INFO]    |  +- org.checkerframework:checker-qual:jar:3.12.0:runtime
[INFO]    |  +- com.google.errorprone:error_prone_annotations:jar:2.11.0:runtime
[INFO]    |  \- com.google.j2objc:j2objc-annotations:jar:1.3:runtime
[INFO]    +- org.apache.commons:commons-lang3:jar:3.12.0:runtime
[INFO]    \- commons-codec:commons-codec:jar:1.15:runtime
[INFO] ------------------------------------------------------------------------

Solution

  • Problem

    Steps

    Copy dependencies to {build_folder}/dependency using maven-dependency-plugin

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>3.6.0</version>
        <executions>
            <execution>
                <id>copy</id>
                <phase>package</phase>
                <goals>
                    <goal>copy-dependencies</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
    

    Add module-info for automatic modules using moditect-maven-plugin

    <plugin>
        <groupId>org.moditect</groupId>
        <artifactId>moditect-maven-plugin</artifactId>
        <version>1.0.0.Final</version>
        <executions>
            <execution>
                <id>add-module-infos</id>
                <phase>generate-resources</phase>
                <goals>
                    <goal>add-module-info</goal>
                </goals>
                <configuration>
                    <outputDirectory>
                        ${project.build.directory}/dependency
                    </outputDirectory>
                    <modules>
                        <module>
                            <artifact>
                                <groupId>commons-io</groupId>
                                <artifactId>commons-io</artifactId>
                                <version>2.13.0</version>
                            </artifact>
                            <moduleInfoSource>
                                module org.apache.commons.io {
                                exports org.apache.commons.io;
                                exports org.apache.commons.io.build;
                                exports org.apache.commons.io.charset;
                                exports org.apache.commons.io.comparator;
                                exports org.apache.commons.io.file;
                                exports org.apache.commons.io.file.attribute;
                                exports org.apache.commons.io.file.spi;
                                exports org.apache.commons.io.filefilter;
                                exports org.apache.commons.io.function;
                                exports org.apache.commons.io.input;
                                exports org.apache.commons.io.input.buffer;
                                exports org.apache.commons.io.monitor;
                                exports org.apache.commons.io.output;
                                exports org.apache.commons.io.serialization;
    
                                }
    
                            </moduleInfoSource>
                        </module>
                    </modules>
                    <overwriteExistingFiles>true</overwriteExistingFiles>
                </configuration>
            </execution>
    
        </executions>
    </plugin>
    

    How to get the <moduleInfoSource> content?

    Create runtime image (with jlink, in the backend)

    launcher class

    public class Launcher {  
        public static void main(String[] args) {  
            HelloApplication.main(args);  
        }  
    }
    

    pom.xml

    <execution>
        <id>create-runtime-image</id>
        <phase>package</phase>
        <goals>
            <goal>create-runtime-image</goal>
        </goals>
        <configuration>
            <jarInclusionPolicy>APP_WITH_DEPENDENCIES</jarInclusionPolicy>
            <modulePath>
                <path>${project.build.directory}/${project.artifactId}-${project.version}.${project.packaging}</path>
                <path>${project.build.directory}/dependency</path>
            </modulePath>
            <modules>
                <module>com.example.soone</module>
            </modules>
            <launcher>
                <name>hello</name>
                <module>com.example.soone/com.example.soone.Launcher</module>
            </launcher>
            <outputDirectory>
                ${project.build.directory}/jlink-image
            </outputDirectory>
        </configuration>
    </execution>
    

    link to demo repository

    Recommendation