javafxports / openjdk-jfx

The openjfx repo has moved to:

Home Page:https://github.com/openjdk/jfx

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

I cannot start JavaFX app with images in FXML in a modular project

Octarine-J opened this issue · comments

When an OpenJFX app uses Java 11 module system and UI is loaded from FXML that contains Images with URLs, it cannot be started. The reason is in this code in javafx/scene/image/Image.java:

if (!URL_QUICKMATCH.matcher(url).matches()) {
    final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
    URL resource;
    if (url.charAt(0) == '/') {
        // FIXME: JIGSAW -- use Class.getResourceAsStream if resource is in a module
        resource = contextClassLoader.getResource(url.substring(1));
    } else {
        // FIXME: JIGSAW -- use Class.getResourceAsStream if resource is in a module
        resource = contextClassLoader.getResource(url);
    }
    if (resource == null) {
        throw new IllegalArgumentException("Invalid URL or resource not found");
    }
    return resource.toString();
}

Fix this already! It has been ages since Java 9 is released, and you still have these FIXMEs ?
This is a major issue, you cannot start the app from the IDE, nor a user can start the app with standard Java 11 options for modular apps (--module-path and --module).

I even can fix it myself if you give me authorization to do so.

We are not aware of any problem that this is causing. If you believe that you have found a bug, then please provide a complete, standalone test program that demonstrates it. If you would like to contribute a fix, even better. Please read CONTRIBUTING.md for instructions on how to proceed.

You need to opens the package containing your images; this allows the JavaFX module to access the resources in said package. Unfortunately, in my experience, you have to use an unqualified opens directive. From my understanding of resource encapsulation it should be enough to use:

opens <package> to javafx.graphics;

But it's not. Maybe @kevinrushforth could shine some light on that. My guess is, since the resource's package must be opened to at least the caller, that it's simply an issue of who's actually the caller in this case.

If you don't want to opens your package, you could always use the @ syntax described in the Location Resolution section of Introduction to FXML.

The location resolution operator (represented by an "@" prefix to the attribute value) is used to specify that an attribute value should be treated as a location relative to the current file rather than a simple string.

This will result in using a valid URL for the Image. In other words, it will have a scheme (e.g. file:, jar:file, etc.). This "bypasses" the resource encapsulation issue.


Note: The same issue applies to CSS files and getStylesheets().add(...). You can use the resource's name but you need to opens the package unconditionally.

Thanks, prefixing with "@" works, but with it all my icon references become like "@../../images/...", which is not very satisfying.

Opening a package also works, and I think it is perfect for me, because all images are confined in a separate package.

It would be nice if the documentation is updated to mention this special treatment of Images inside FXML.

It would be nice if the documentation is updated to mention this special treatment of Images inside FXML.

This does seem like something that would be good to document. We already document that you need to open your package to javafx.fxml.

Before I file the doc bug, could you also test whether opening your package to both javafx.fxml and javafx.graphics works? If so, that would seem preferable to using @ to indicate relative file paths.

    opens <package> to javafx.fxml, javafx.graphics;

Just tested it. Unfortunately, qualified opens does not seem to work.
So now I have

opens org.app.gui.images;
opens org.app.gui.styles;

Which is still OK for me, since these packages contain only images/styles.

Just tested it. Unfortunately, qualified opens does not seem to work.

This seems like a bug, probably similar in nature to JDK-8177566.

I'll file a JBS bug to track this. The workaround, as you discovered, is to unconditionally open the package containing your images.

We already document that you need to open your package to javafx.fxml.

Want to point out that this isn't just an issue with FXML. Trying to use a resource path programmatically doesn't work either, unless you opens the package unconditionally (or use Class#getResource). In other words, neither of the following work with qualified opens directives:

  • Image image = new Image("resource/path/to/image.png");
  • sceneOrParent.getStylesheets().add("resource/path/to/file.css");

However, I don't know if there's an elegant way to fix this problem. Especially a fix that will work for both. The Image constructor might be able to determine who the caller Module was, and thus use Module.getResourceAsStream, but would this work when it's the javafx.fxml module creating the object? And how would this work with stylesheets? Since it's added to a list and read at some later time, I don't think it's possible to tell what Module the resource belongs to.

In which case, I think the only option is to opens the package unconditionally if one wants to use the resource name rather than the full URL.

Please note, however, that I am not an expert in the module system.


I bring this up as a potential issue in the first place because both Image and getStylesheets document being able to resolve a path as relative to the application's classpath, but this doesn't appear to work as cleanly when using the modulepath.

Oh, I see. You are right that this isn't the same case as the FXML case, where it was an internal "trampoline" class running in the unnamed module that caused the problem.

I'll still take a closer look, but this may well end up being something to document rather than a bug to fix.

@kevinrushforth any updates on this issue?

While trying to convert my large project over to modules, I got stuck on this exact same thing. However in my case, the resources in question lived in a different .jar file of their own, which I was adding to the modular project using --patch-module. I don't know how to use --add-opens to create an unqualified opens (it's impossible, right?). So I believe I'll be forced to rearrange my project build so that the resources are in a .jar file which also has a module-info. I hope that this bug can get fixed in a way that helps avoid that kind of roadblock.