javajavafxmaven-shade-plugin

JavaFX build with shade, LoadException?


After following this SO post (JavaFX build with shade, Location is required. Where is it looking?), I have configured my project to be the same. But when running:

java -jar .\target\app-name-1.0.0-ALPHA.jar

I get the following exception:

Exception in Application start method
Exception in thread "main" java.lang.RuntimeException: Exception in Application start method
        at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:901)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:196)
        at java.base/java.lang.Thread.run(Thread.java:1589)
Caused by: javafx.fxml.LoadException:
file:/C:/Users/user/Documents/app-name/target/app-name-1.0.0-ALPHA.jar!/view/rename.fxml:14
...
Caused by: java.lang.NullPointerException: Location is required.

I load the fxml file like this:

final Parent root = FXMLLoader.load(getClass().getResource("/view/rename.fxml"));

INPUT:

src/main/java/app-name/AppLauncher.java (class that does not extend Application)
src/main/java/app-name/AppMain.java     (class that does extend Application)
src/main/resources/view/rename.fxml

OUTPUT:

target/classes/app-name/AppLauncher.class
target/classes/app-name/AppMain.class
target/classes/view/rename.fxml
target/classes/style/style.css
target/app-name-1.0.0-ALPHA.jar

pom.xml:

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <javafx.version>19</javafx.version>
</properties>

<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-shade-plugin -->
<dependency>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.4.1</version>
</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>

<!-- JavaFX: cross-platform fat jar -->
<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-graphics</artifactId>
    <version>${javafx.version}</version>
    <classifier>win</classifier>
</dependency>
<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-graphics</artifactId>
    <version>${javafx.version}</version>
    <classifier>linux</classifier>
</dependency>
<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-graphics</artifactId>
    <version>${javafx.version}</version>
    <classifier>mac</classifier>
</dependency>
</dependencies>

<build>
<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.10.1</version>
        <configuration>
            <release>19</release>
        </configuration>
    </plugin>

    <!-- For fat jar -->
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.4.1</version>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>shade</goal>
                </goals>
                <configuration>
                    <transformers>
                        <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                            <mainClass>uk.co.conoregan.showrenamer.ShowRenamerLauncher</mainClass>
                        </transformer>
                    </transformers>
                </configuration>
            </execution>
        </executions>
    </plugin>
</plugins>
</build>

And to package, I am using:

mvn clean package

which does show an error, but after reading it I assumed it was not the cause of my issue:

checker-qual-3.5.0.jar, commons-codec-1.10.jar, commons-collections4-4.2.jar, commons-dbcp-1.4.jar, commons-io-2.6.jar, commons-lang3-3.3.2.jar, commons-pool-1.6.jar, jdependency-2.8.0.jar, jdom2-2.0.6.1.jar define 1 overlapping resource: 
  - META-INF/LICENSE.txt
javafx-graphics-19-linux.jar, javafx-graphics-19-mac.jar define 320 overlapping classes and resources: 
  - com.sun.prism.es2.BufferFactory
  - com.sun.prism.es2.ES2Context
  - com.sun.prism.es2.ES2Context$1
  - com.sun.prism.es2.ES2Graphics
  - com.sun.prism.es2.ES2Light
  - com.sun.prism.es2.ES2Mesh
  - com.sun.prism.es2.ES2Mesh$ES2MeshDisposerRecord
  - com.sun.prism.es2.ES2MeshView
  - com.sun.prism.es2.ES2MeshView$ES2MeshViewDisposerRecord
  - com.sun.prism.es2.ES2PhongMaterial
  - 310 more...
commons-codec-1.10.jar, commons-collections4-4.2.jar, commons-dbcp-1.4.jar, commons-io-2.6.jar, commons-lang3-3.3.2.jar, commons-pool-1.6.jar, jdependency-2.8.0.jar define 1 overlapping resource: 
  - META-INF/NOTICE.txt
javafx-graphics-19-linux.jar, javafx-graphics-19-mac.jar, javafx-graphics-19-win.jar define 2828 overlapping classes and resources: 
  - META-INF/substrate/config/jniconfig-aarch64-android.json
  - META-INF/substrate/config/jniconfig-aarch64-darwin.json
  - META-INF/substrate/config/jniconfig-aarch64-linux.json
  - META-INF/substrate/config/jniconfig-arm64-ios.json
  - META-INF/substrate/config/jniconfig-x86_64-darwin.json
  - META-INF/substrate/config/jniconfig-x86_64-ios.json
  - META-INF/substrate/config/jniconfig-x86_64-linux.json
  - META-INF/substrate/config/jniconfig-x86_64-windows.json
  - META-INF/substrate/config/jniconfig.json
  - META-INF/substrate/config/reflectionconfig-aarch64-darwin.json
  - 2818 more...
commons-logging-1.1.1.jar, jcl-over-slf4j-1.7.7.jar define 6 overlapping classes: 
  - org.apache.commons.logging.Log
  - org.apache.commons.logging.LogConfigurationException
  - org.apache.commons.logging.LogFactory
  - org.apache.commons.logging.impl.NoOpLog
  - org.apache.commons.logging.impl.SimpleLog
  - org.apache.commons.logging.impl.SimpleLog$1
javafx-base-19-win.jar, javafx-controls-19-win.jar, javafx-fxml-19-win.jar, javafx-graphics-19-linux.jar, javafx-graphics-19-mac.jar, javafx-graphics-19-win.jar define 1 overlapping resource: 
  - META-INF/substrate/config/reflectionconfig.json
maven-shade-plugin has detected that some class files are
present in two or more JARs. When this happens, only one
single version of the class is copied to the uber jar.
Usually this is not harmful and you can skip these warnings,
otherwise try to manually exclude artifacts based on
mvn dependency:tree -Ddetail=true and the above output.
See https://maven.apache.org/plugins/maven-shade-plugin/

Have I done something wrong?


Solution

  • The issue was related to loading a .properties file in the static block of a class, and that class was loaded by the controller. I didn't include it in the example as I didn't think it was relevant, but I guess I was wrong.

    The code that failed:

    ...
    private static final String API_KEY;
    
    static {
        final String apiKeysPath = "properties/api_keys.properties";
        final URL res = MyClass.class.getClassLoader().getResource(apiKeysPath);
        if (res == null) {
            throw new UncheckedIOException(new FileNotFoundException(apiKeysPath));
        }
    
        final URI uri;
        try {
            uri = res.toURI();
        } catch (URISyntaxException ex) {
            throw new IllegalArgumentException(ex);
        }
    
        final Properties properties = new Properties();
        try (InputStream is = Files.newInputStream(Paths.get(uri))) {
            properties.load(is);
        } catch (IOException e) {
            throw new UncheckedIOException("Failed to load resource", e);
        }
    
        API_KEY = properties.getProperty("API_KEY");
    }
    ...
    

    The code that works:

    ...
    private static final String API_KEY;
    
    static {
        final String apiKeysPath = "/properties/api_keys.properties";
        final InputStream res = MyClass.class.getResourceAsStream(apiKeysPath);
    
        final Properties properties = new Properties();
        try {
            properties.load(res);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    
        API_KEY = properties.getProperty("API_KEY");
    }
    ...
    

    Maybe this can help someone in the future.