Java Modules: Nein Danke!

Die Java Module, die mit Java 9 eingeführt wurden, sahen erst einmal fantastisch aus.

Zusammen mit JLink und JPackage konnte man seine modularisierten Java Anwendungen auch gut aufbereiten zur Weitergabe an Dritte.

Aber wie so oft, kommt da ein großes ABER: In der “Enterprise-Welt” wurden diese Module mehr oder weniger ignoriert. Jakarta EE, Spring Framework, … wollten davon (lange Zeit) nichts wissen. Erst mit den neuesten Versionen wird hier auch das Modulsystem unterstützt (Jakarta EE 10, Spring Framework 6, …)

Die Folge davon war, dass nicht nur die sehr alten Abhängigkeiten aber auch alle Abhängigkeiten, die erst zur Laufzeit dazu kommen, das Modulsystem nicht unterstützen. Die JAR Dateien hatten also keine module-info.class und damit wurden diese per “Auto-Module” eingebunden.

Soweit alles ganz toll und noch kein Problem sichtbar, aber wenn man dann mit JLink ein Image bauen wollte, war man aufgeschmissen: JLink unterstützt keine Auto-Module. Damit musste man hier nachträglich noch eine module-info.class unterschieben. Technisch kein Problem: jdeps kann dies bei Bedarf erstellen und mit Hilfe des Moditect Plugins liess sich das auch in Maven gut einbinden.

Somit gab es eine technische Lösung, die sich auch in Projekten umsetzen liess. Es sah so aus, als ob dieses Vorgehen so nun notwendig wäre und wurde von mir entsprechend umgesetzt und auch im Java Forum entsprechend als mögliches Vorgehen beworben.

Die immer wieder damit verbundenen Probleme haben aber klar aufgezeigt, dass dieser Weg alles andere als gut und gangbar ist. Und nach all den vielen Stunden Aufwand zeigt sich nun, dass es auch eine relativ einfache Lösung für diese Thematik gibt:

a) Wir bauen eine Anwendung ganz ohne eine Modulbeschreibung! Also keine module-info.java, keine Notwendigkeit, hier immer die requires Einträge zu machen (In IntelliJ immer relativ blöd, da man sich da etwas im Kreis dreht: Intellij schlägt bei einem import im Java File den requires Eintrag vor so dieser fehlt. Aber ohne den requires EIntrag erzeugt IntelliJ keinen import Eintrag für eine Klasse. Eins von beidem muss man schreiben. Ich lasse aber gerne beides direkt von IntelliJ erstellen.)

b) Bei JavaFX benötigen wir noch eine weitere Klasse mit einer main Methode. Üblicherweise startet man eine JavaFX Anwendung mit einer Klasse, die von Application erbt und die dann u.a. eine main Methode hat, die nur launch aufruft. Dies klappt leider bei JavaFX nicht, so dass eine weitere Klasse benötigt wird deren main mMthode dann nur die sonst übliche main Methode aufruft. Klingt dubios, aber ist leider so notwendig (die technischen Hintergründe erspare ich uns hier!)

c) Im Maven Projekt bauen wir nun noch etwas ein, dass die Abhängigkeiten für uns kopiert. Dabei wollen wir natürlich auch alle Abhängigkeiten mit Scope Runtime kopieren:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>${maven.dependency.plugin}</version>
    <executions>
        <!-- erstmal Abhängigkeiten für den Class-Path kopieren -->
        <execution>
            <id>copy-dependencies</id>
            <phase>package</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <outputDirectory>${project.build.directory}/modules</outputDirectory>
                <includeScope>runtime</includeScope>
                <overWriteReleases>false</overWriteReleases>
                <overWriteSnapshots>false</overWriteSnapshots>
                <overWriteIfNewer>true</overWriteIfNewer>
            </configuration>
        </execution>

        <!-- dazu noch das Projekt-JAR -->
        <execution>
            <id>copy</id>
            <phase>install</phase>
            <goals>
                <goal>copy</goal>
            </goals>
            <configuration>
                <outputDirectory>${project.build.directory}/modules</outputDirectory>
                <artifactItems>
                    <artifactItem>
                        <groupId>${project.groupId}</groupId>
                        <artifactId>${project.artifactId}</artifactId>
                        <version>${project.version}</version>
                        <type>${project.packaging}</type>
                        <destFileName>${project.build.finalName}.jar</destFileName>
                    </artifactItem>
                </artifactItems>
                <overWriteIfNewer>true</overWriteIfNewer>
            </configuration>
        </execution>
    </executions>
</plugin>

d) Nun kann noch JPackage aufgerufen werden. Ganz ohne vorherigen JLink Aufruf:

<plugin>
    <groupId>com.github.akman</groupId>
    <artifactId>jpackage-maven-plugin</artifactId>
    <version>${jpackage.maven.plugin}</version>
    <configuration>
        <name>${appName}</name>
        <type>IMAGE</type>
        <mainclass>${main.class}</mainclass>
        <input>${project.build.directory}/modules</input>
        <mainjar>${jar.file}.jar</mainjar>
    </configuration>
    <executions>
        <execution>
            <phase>install</phase>
            <goals>
                <goal>jpackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Die einzelnen Properties müssen natürlich gesetzt sein. Beispielprojekte, die diese Nutzung aufzeigen, finden sich unter:

Bei Fragen und Anregungen stehe ich gerne per Email zur Verfügung oder kommt einfach in das Java Forum!