1

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?

Conor Egan
  • 518
  • 1
  • 3
  • 21

1 Answers1

1

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.

Conor Egan
  • 518
  • 1
  • 3
  • 21
  • 3
    As you discovered the hard way, static blocks in Java code are almost always a bad idea. When there is an uncaught exception in a static block, the class fails to load. If that happens, it can cause weird and unforeseen consequences. Even your updated code is a [bad idea](https://stackoverflow.com/questions/2070293/why-doesnt-java-allow-to-throw-a-checked-exception-from-static-initialization-b#comment54228450_15289277). If your properties fail to load, the loading class and likely your app will be unrecoverably broken My suggestion is to never write code in static blocks. – jewelsea Apr 06 '23 at 07:07
  • 1
    @jewelsea even taking into account the everything-might-happen-after-static-initializers-throw still wondering how the outcome of the first version here would lead to a location-not-found (which always is a broken resource lookup when loading a fxml) – kleopatra Apr 06 '23 at 09:30
  • Okay thank you for the suggestion, I wasn't aware of that. I'll look into a better solution for loading properties! – Conor Egan Apr 06 '23 at 17:51
  • And for the line that failed, it was `try (InputStream is = Files.newInputStream(Paths.get(uri)))`. Not sure why, however. – Conor Egan Apr 06 '23 at 17:52
  • 1
    File-based access to resources will fail if the resource is not a `File`. If you build a jar of your application, then the resource is a jar entry accessed via the [`jar:` protocol](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/net/JarURLConnection.html), not a file accessed via the `file:` protocol. You are using shade to put everything in a jar, so file-based access to the resource fails. As a rule, never try to read a resource using the `File` protocol, instead use only the resource access methods from the class or class loader. – jewelsea Apr 06 '23 at 21:10