0

For context, I have a Maven Project Structure in Intellij and also the /target/classes/

The code where I am getting error:

@Override
    public void start(Stage stage) throws IOException {

        Parent root;
        FXMLLoader fxmlLoader = new FXMLLoader();

        // Below line has the problem of loading image inside fxml file.
        root = fxmlLoader.load(getClass().getClassLoader().getResourceAsStream("AuthorizationPage.fxml"));

        // not used in code but below line returns inputstream object which is not null so the image loads fine
           when independently verified.
        // System.out.println(Objects.isNull(getClass().getClassLoader().getResourceAsStream("sign-up-logo.png")));

        //Below Line works fine and loads fxml file correctly with images.
        root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("/AuthorizationPage.fxml")));

        Scene scene = new Scene(root);
        stage.setScene(scene);

        SceneManager sceneManager = SceneManager.getInstance(stage);
        sceneManager.addScene("/AuthorizationPage.fxml", "User Authorization");

        if (!SessionManager.hasUserSessionFromLocalFileExpired()) {

            sceneManager = SceneManager.getInstance(stage);
            sceneManager.addScene("/convertor.fxml", "Convert Pdf Documents");
            sceneManager.activateScene("Convert Pdf Documents", "Convert Pdf Documents", true, true);
        } else {

            sceneManager.activateScene("User Authorization", "User Authorization", false, false);
        }
    }

The stacktrace for the exception:

null/images/sign-up-logo.png
Exception in Application start method
java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at javafx.graphics@19.0.2/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:465)
    at javafx.graphics@19.0.2/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:364)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1082)
Caused by: java.lang.RuntimeException: Exception in Application start method
    at javafx.graphics@19.0.2/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:901)
    at javafx.graphics@19.0.2/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:196)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: javafx.fxml.LoadException: 
unknown path:33

    at javafx.fxml@19.0.2/javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2714)
    at javafx.fxml@19.0.2/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2692)
    at javafx.fxml@19.0.2/javafx.fxml.FXMLLoader.load(FXMLLoader.java:2539)
    at com.javafxapp.javafxapplication/com.app.convertor.authorization.Main.start(Main.java:39)
    at javafx.graphics@19.0.2/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:847)
    at javafx.graphics@19.0.2/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:484)
    at javafx.graphics@19.0.2/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at javafx.graphics@19.0.2/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456)
    at javafx.graphics@19.0.2/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at javafx.graphics@19.0.2/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics@19.0.2/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
    ... 1 more
Caused by: java.lang.IllegalArgumentException: Invalid URL: Invalid URL or resource not found
    at javafx.graphics@19.0.2/javafx.scene.image.Image.validateUrl(Image.java:1138)
    at javafx.graphics@19.0.2/javafx.scene.image.Image.<init>(Image.java:695)
    at javafx.fxml@19.0.2/com.sun.javafx.fxml.builder.JavaFXImageBuilder.build(JavaFXImageBuilder.java:47)
    at javafx.fxml@19.0.2/com.sun.javafx.fxml.builder.JavaFXImageBuilder.build(JavaFXImageBuilder.java:37)
    at javafx.fxml@19.0.2/javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:774)
    at javafx.fxml@19.0.2/javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2961)
    at javafx.fxml@19.0.2/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2646)
    ... 11 more
Caused by: java.lang.IllegalArgumentException: Invalid URL or resource not found
    at javafx.graphics@19.0.2/javafx.scene.image.Image.validateUrl(Image.java:1123)
    ... 17 more
Exception running application com.app.convertor.authorization.Main

Process finished with exit code 1

fxml snippet:

<HBox prefHeight="100.0" prefWidth="200.0">
                     <children>
                        <AnchorPane prefHeight="200.0" prefWidth="200.0" HBox.hgrow="ALWAYS">
                           <children>
                              <ImageView fitHeight="108.0" fitWidth="101.0" layoutX="270.0" pickOnBounds="true" preserveRatio="true">
                                 <image>
                                    <Image url="@images/sign-up-logo.png" />
                                 </image>
                              </ImageView>
                           </children>
                        </AnchorPane>
                     </children>
                  </HBox>

Maven pom file snippet:

<resources>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
            <resource>
                <directory>src/main/resources/com/app/convertor/config</directory>
            </resource>
            <resource>
                <directory>src/main/resources/com/app/convertor/fxml/images</directory>
            </resource>
            <resource>
                <directory>src/main/resources/com/app/convertor/fxml</directory>
            </resource>
</resources>

Why the image fails to load with getResourcesAsStream() method but works fine using getResources() method? I checked if getResourcesAsStream() returns null, but it identifies fxml file correctly from resource folder. What I am getting wrong and what should I do to load fxml file correctly? There are similar questions asked but none of them helped me.

Vidhan
  • 11
  • 1
  • 3
  • 1
    *“There are similar questions asked but none of them helped me.”* -> edit the question, link them. Follow a pattern from the [eden JavaFX resource guide](https://edencoding.com/where-to-put-resource-files-in-javafx/). You don’t need (should not have) a resources section in your pom.xml. No useful guide would have done that. – jewelsea Feb 15 '23 at 16:24
  • 2
    It's not the cause of the problem, but specifying multiple resource directories, some of which are subdirectories of others, is highly confusing, and I'm not sure how this will be handled. Specifying just the top-level directory (`src/main/resources`) will include all subdirectories, so just the first `` entry should suffice. Since that is the default, omitting the `` entirely from the pom.xml will produce the correct results. – James_D Feb 15 '23 at 16:33
  • *"I'm not sure how this will be handled. "* Actually, looking at your screenshots, this is simply being taken literally. Note that the content of all the directories you specified are being copied into the root of the classpath. This means elements will be duplicated (or worse): you have `sign-up-logo.png` in the root of the classpath (from `src/main/resources/com/app/converter/fxml/images`), in `images/sign-up-logo.png` (from `src/main/resources/com/app/converter/fxml`), and in the correct place, `com/app/converter/fxml/images/sign-up-logo.png` (from `/src/main/resources`). – James_D Feb 15 '23 at 16:46
  • I have implemented your suggestions and everything (except files under config folder) is working fine with no duplicates files either. I am unable to obtain URL object for files under config folder whereas fxml files under fxml folder are being recognized. – Vidhan Feb 15 '23 at 17:40
  • Config handing is a different issue, you can ask a new question (with a custom, complete and specific [mcve]) specifically about that if needed. Also there are various questions on StackOverflow on options for dealing with config. Often config needs to be modified post deployment, which means the default config needs to be extracted from a jar or packaged separately and read from an external read/write location rather than a read only location in a jar. – jewelsea Feb 15 '23 at 19:24
  • I understand. I have followed the guide you provided and it was very helpful. I recommend future visitors to follow the guide for solving related issues. – Vidhan Feb 16 '23 at 16:31

1 Answers1

5

Why the image fails to load with getResourcesAsStream() method but works fine using getResources() method?

Using URL location resolution in the FXML file:

<Image url="@images/sign-up-logo.png" />

will attempt to resolve the URL relative to the location of the FXML file being parsed.

That location is only set if you use one of the FXML loading methods specifying a location, e.g.

URL url = ... ;
FMXLLoader.load(url);

or

URL url = ... ;
FXMLLoader loader = new FXMLLoader(url);
loader.load();

etc.

If you load the FXML specifying an InputStream, such as is returned by getClass().getResourceAsStream(), then the FXMLLoader will not have a location specified and will not be able to resolve any URLs specified in there. (In theory, the stream could even be dynamically generated, and might not even correspond to a location at all.)

You can see this from part of the stack trace:

null/images/sign-up-logo.png

Here the FXMLLoader is appending /images/sign-up-logo.png to the location of the FXML; since that location is null (because you are loading by specifying a stream), the location makes no sense and the image cannot be loaded.

The solution is just to specify the URL (e.g. with getClass().getResource()), instead of a stream (e.g. with getClass().getResourceAsStream()).

If you really need to pass a stream, for some reason, and you are using location resolution in the FXML file, then the following should work:

String loc = ... ;
URL location = getClass().getResource(loc);
InputStream stream = getClass().getResourceAsStream(loc);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(location);
Parent root = loader.load(stream);

However, this is a strange usage. The code above is equivalent to

String loc = ... ;
URL location = getClass().getResource(loc);
FXMLLoader loader = new FXMLLoader(loc);
Parent root = loader.load();

which I think is far preferable.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thankyou. I have got more clarity on the topic as a whole as I am still new in Java and Javafx world. As for why I am looking for this specific way is because somewhere I read that in JAR executable, getResources() method may not work whereas getResourcesAsStream() method will work. – Vidhan Feb 15 '23 at 17:36
  • 1
    @Vidhan *"I read that in JAR executable, getResources() method may not work whereas getResourcesAsStream() method will work."*. That's not true, as long as you are using a valid resource path. See https://stackoverflow.com/questions/61531317/how-do-i-determine-the-correct-path-for-fxml-files-css-files-images-and-other as well as the link in the first comment under the question. – James_D Feb 15 '23 at 19:22