Search code examples
javamavenintellij-ideajavafxmulti-module

Multi-module JavaFX maven project packaging issue


This is an attempt to create a multi-module JavaFX application with maven.

Given the following structure of the project:

project
|  pom1.xml
|_____ Word Generator (Folder)
      |  pom2.xml
      |_____logic (folder)
            |  WordGenerator
|_____UI (folder)
      |  pom3.xml
      |_____marty  
            |  App
            |  PrimaryController
            |  SecondaryController

We have the following structure of the pom files in order of the scheme above:

pom1.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>

    <groupId>org.games.marty</groupId>
    <artifactId>words</artifactId>
    <packaging>pom</packaging>
    <version>0.1</version>

    <modules>
        <module>UI</module>
        <module>Word Generator</module>
    </modules>

    <properties>
        <maven.compiler.source>16</maven.compiler.source>
        <maven.compiler.target>16</maven.compiler.target>
    </properties>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                    <configuration>
                        <source>16</source>
                        <target>16</target>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

pom2.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">
    <parent>
        <artifactId>words</artifactId>
        <groupId>org.games.marty</groupId>
        <version>0.1</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>word.generator</artifactId>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>16</maven.compiler.source>
        <maven.compiler.target>16</maven.compiler.target>
    </properties>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                    <configuration>
                        <source>16</source>
                        <target>16</target>
                    </configuration>
                </plugin>
                <plugin>
                    <!-- Build an executable JAR -->
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.1.0</version>
                    <configuration>
                        <archive>
                            <manifest>
                                <addClasspath>true</addClasspath>
                                <classpathPrefix>lib/</classpathPrefix>
                                <mainClass>org.games.marty.logic.WordGenerator</mainClass>
                            </manifest>
                        </archive>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

pom3.xml

<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <artifactId>UI</artifactId>
    <version>0.1</version>

    <parent>
        <artifactId>words</artifactId>
        <groupId>org.games.marty</groupId>
        <version>0.1</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>16</maven.compiler.source>
        <maven.compiler.target>16</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>16</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>16</version>
        </dependency>
        <dependency>
            <groupId>org.games.marty</groupId>
            <artifactId>word.generator</artifactId>
            <version>0.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>16</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.6</version>
                <executions>
                    <execution>
                        <!-- Default configuration for running -->
                        <!-- Usage: mvn clean javafx:run -->
                        <id>default-cli</id>
                        <configuration>
                            <mainClass>org.games.marty.App</mainClass>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <!-- Build an executable JAR -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>org.games.marty.App</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

The way we have attempted to build the application in order for the UI to have access to the WordGenerator logic is to maven package the result from the pom1.xml directive

We get the above error as mentioned earlier:

Error: Could not find or load main class org.games.marty.App
Caused by: java.lang.NoClassDefFoundError: javafx/application/Application

As far as my understanding goes, the JavaFX dependencies are installed throught maven and should be available but they are missing?


Solution

  • Packaging via mvn package using the maven-jar-plugin is not enough

    mvn package, by default, is just going to package the jar for your application code, it isn't going to include all of the dependant library code (which is why the dependent code cannot be found when you attempt to run your application).

    You could package your application code and dependant libraries using an assembly, as detailed in How can I create an executable JAR with dependencies using Maven?, though that approach is not the only one to solve your problem.

    You need to build some kind of runtime image

    There are numerous options for building runtime images and I don't know your requirements, so I can't recommend what you should do instead. Example options are:

    1. A zip/tar of your application plus libraries in a separate directory.
    2. The creation of a single jar that includes all dependant code.
    3. Either of solutions 1 or 2, plus the inclusion of a packaged JRE.
    4. A runtime image with your code and libraries which also uses just the custom runtime portions of the JRE and JavaFX modules you need (using jlink).
    5. A native installer for either 3 or 4 (using jpackage + a native installer creation tool, e.g. WIX, RPM, DEB installer creators).

    The last method (native installer), is the packaging, distribution, and installation method I would recommend for most non-trivial applications.

    You need to research how to do this

    To get your solution, you will need to do your own research, and, once you have chosen an approach and toolset, you could create a new question regarding the implementation of that approach, if you continue to have difficulties.

    Related resources

    Warning for shaded jars

    If you bundle all JavaFX code into a single jar using the maven shade plugin, you will get a warning like the following when you run your application from Java 16+:

    WARNING: Unsupported JavaFX configuration: classes were loaded from 'unnamed module @28c71909'

    This indicates that such a configuration is not supported, and may (and probably will) break in future and perhaps current JavaFX platform revisions. Thus, shaded jars that include JavaFX platform code are not recommended by me, even though such jars might currently work for your deployments.

    JavaFX 11+ is built to be used as a set of modules. Configurations are not supported if they do not run the JavaFX platform off of the module path but instead run the platform code off of the classpath (as a shaded jar would).