javafx

How do I determine the correct path for FXML files, CSS files, Images, and other resources needed by my JavaFX Application?


My JavaFX application needs to be able to find the FXML files to load them with the FXMLLoader, as well as stylesheets (CSS files) and images. When I try to load these, I often get errors, or the item I'm trying to load simply doesn't load at runtime.

For FXML files, the error message I see includes

Caused by: java.lang.NullPointerException: location is not set

For images, the stack trace includes

Caused by: java.lang.IllegalArgumentException: Invalid URL: Invalid URL or resource not found

How do I figure out the correct resource path for these resources?


Solution

  • Short version of answer:


    Full Answer

    Contents

    1. Scope of this answer
    2. Resources are loaded at runtime
    3. JavaFX uses URLs to load resources
    4. Resources as streams
    5. Rules for resource names
    6. Creating a resource URL with getClass().getResource(...)
    7. Organizing code and resources
    8. Maven (and similar) standard layouts
    9. Troubleshooting

    Scope of this answer

    Note that this answer only addresses loading resources (for example FXML files, images, and stylesheets) that are part of the application, and bundled with it. So, for example, loading images that the user chooses from the file system on the machine on which the application is running would require different techniques that are not covered here.

    Resources are loaded at runtime

    The first thing to understand about loading resources is that they, of course, are loaded at runtime. Typically, during development, an application is run from the file system: that is, the class files and resources required to run it are individual files on the file system. However, once the application is built, it is usually executed from a jar file. In this case, the resources such as FXML files, stylesheets, and images, are no longer individual files on the filesystem but are entries in the jar file. Therefore:

    Code cannot use File, FileInputStream, or file: URLs to load a resource

    JavaFX uses URLs to load resources

    JavaFX loads FXML, Images, and CSS stylesheets using URLs.

    The FXMLLoader explicitly expects a java.net.URL object to be passed to it (either to the static FXMLLoader.load(...) method, to the FXMLLoader constructor, or to the setLocation() method).

    Both Image and Scene.getStylesheets().add(...) expect Strings that represent URLs. If URLs are passed without a scheme, they are interpreted relative to the classpath. These strings can be created from a URL in a robust way by calling toExternalForm() on the URL.

    The recommended mechanism for creating the correct URL for a resource is to use Class.getResource(...), which is called on an appropriate Class instance. Such a class instance can be obtained by calling getClass() (which gives the class of the current object), or ClassName.class. The Class.getResource(...) method takes a String representing the resource name.

    Resources as streams

    If you need the resource as a stream, you can use getClass().getResourceAsStream(...) or SomeOtherClass.class.getResourceAsStream(...) to create a stream for the resource data. However, for most work in JavaFX you don't need this as the JavaFX APIs mainly work with URLs as input rather than streams. There are some APIs such as Image constructors and the FXMLLoader that can work with both URLs and streams. For such APIs it is often better to use the URL-based API forms rather than the stream-based API forms. This allows processes like the FXMLLoader to find relative URLs to included resources which it cannot otherwise do if just presented a raw InputStream.

    Rules for resource names

    The last point has an important consequence:

    . and .. are not valid Java identifiers, so they cannot be used in resource names.

    These may actually work when the application is running from the filesystem, though this is really more of an accident of the implementation of getResource(). They will fail when the application is bundled as a jar file.

    Similarly, if you are running on an operating system that does not distinguish between filenames that differ only by case, then using the wrong case in a resource name might work while running from the filesystem, but will fail when running from a jar file.

    Resource names beginning with a leading / are absolute: in other words they are interpreted relative to the classpath. Resource names without a leading / are interpreted relative to the class on which getResource() was called.

    A slight variation on this is to use getClass().getClassLoader().getResource(...). The path supplied to ClassLoader.getResource(...) must not begin with a / and is always absolute, i.e. it is relative to the classpath. It should also be noted that in modular applications, access to resources using ClassLoader.getResource() is, under some circumstances, subject to rules of strong encapsulation, and additionally the package containing the resource must be opened unconditionally. See the documentation for details.

    Resources loaded from FXML

    In FXML, attributes which are interpreted as URLs (such as those passed to an image attribute) are interpreted either as strings to be passed to ClassLoader::getResource (in which case they must follow the rules in the previous paragraph), or strings passed to either a File or URL constructor. FXML also supports a special "location resolution" operator, @, from which the FXMLLoader will generate a URL relative to the current FXML file. (These will then be canonicalized, so it is valid to use .. in conjunction with the location resolution operator as the .. will be removed in the process of canonicalization.) See this answer for some more details and examples.

    Creating a resource URL with getClass().getResource()

    To create a resource URL, use someClass.getResource(...). Usually, someClass represents the class of the current object, and is obtained using getClass(). However, this doesn't have to be the case, as described in the next section.

    Organizing code and resources

    I recommend organizing your code and resources into packages determined by the part of the UI they are associated with. The following source layout in Eclipse gives an example of this organization:

    enter image description here

    Using this structure, each resource has a class in the same package, so it is easy to generate the correct URL for any resource:

    FXMLLoader editorLoader = new FXMLLoader(EditorController.class.getResource("Editor.fxml"));
    Parent editor = editorLoader.load();
    FXMLLoader sidebarLoader = new FXMLLoader(SidebarController.class.getResource("Sidebar.fxml"));
    Parent sidebar = sidebarLoader.load();
    
    ImageView logo = new ImageView();
    logo.setImage(newImage(SidebarController.class.getResource("logo.png").toExternalForm()));
    
    mainScene.getStylesheets().add(App.class.getResource("style.css").toExternalForm());
    

    If you have a package with only resources and no classes, for example, the images package in the layout below

    enter image description here

    you can even consider creating a "marker interface" solely for the purposes of looking up the resource names:

    package org.jamesd.examples.sample.images ;
    public interface ImageLocation { }
    

    which now lets you find these resources easily:

    Image clubs = new Image(ImageLocation.class.getResource("clubs.png").toExternalForm());
    

    Loading resources from a subpackage of a class is also reasonably straightforward. Given the following layout:

    enter image description here

    we can load resources in the App class as follows:

    package org.jamesd.examples.resourcedemo;
    import java.net.URL;
    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    public class App extends Application {
        @Override
        public void start(Stage primaryStage) throws Exception {
                    
            URL fxmlResource = getClass().getResource("fxml/MainView.fxml");
            
            Parent root = FXMLLoader.load(fxmlResource);
            Scene scene = new Scene(root);
            scene.getStylesheets().add(getClass().getResource("style/main-style.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
        }
        
        public static void main(String[] args) {
            Application.launch(args);
        }
    }
    

    To load resources which are not in the same package, or a subpackage, of the class from which you're loading them, you need to use the absolute path:

        URL fxmlResource = getClass().getResource("/org/jamesd/examples/resourcedemo/fxml/MainView.fxml");
    

    Maven (and similar) standard layouts

    Maven and other dependency management and build tools recommend a source folder layout in which resources are separated from Java source files, as per the Maven Standard Directory Layout. The Maven layout version of the previous example looks like:

    enter image description here

    It is important to understand how this is built to assemble the application:

    In this example, because the resources are in folders that correspond to subpackages of the packages where the source code is defined, the resulting build (which, by default with Maven, is in target/classes) consists of a single structure.

    Note that both src/main/java and src/main/resources are considered the root for the corresponding structure in the build, so only their content, not the folders themselves, are part of the build. In other words, there is no resources folder available at runtime. The build structure is shown below in the "troubleshooting" section.

    Notice that the IDE in this case (Eclipse) displays the src/main/java source folder differently from the src/main/resources folder; in the first case it displays packages, but for the resource folder it displays folders. Make sure you know if you are creating packages (whose names are .-delimited) or folders (whose names must not contain ., or any other character not valid in a Java identifier) in your IDE.

    If you are using Maven and decide that for ease of maintenance you'd rather keep your .fxml files next to the .java files that reference them (instead of sticking strictly to the Maven Standard Directory Layout), you can do so. Just tell Maven to copy these files to the same folder in your output directory that it will place the class files generated from those source files into, by including something like the following in your pom.xml file:

        <build>
            ...
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.fxml</include>
                    <include>**/*.css</include>
                </includes>
            </resource>
            ...
        </build>
    

    If you do this, you can then use an approach like FXMLLoader.load(getClass().getResource("MyFile.fxml")) to have your classes load .fxml resources from the directory which contains their own .class files.

    Troubleshooting

    If you get errors you do not expect, first check the following:

    If all this seems correct, and you still see errors, check the build or deployment folder. The exact location of this folder will vary by IDE and build tool. If you are using Maven, by default it is target/classes. Other build tools and IDEs will deploy to folders named bin, classes, build, or out.

    Often, your IDE will not show the build folder, so you may need to check it with the system file explorer.

    The combined source and build structure for the Maven example above is

    enter image description here

    If you are generating a jar file, some IDEs may allow you to expand the jar file in a tree view to inspect its contents. You can also check the contents from the command line with jar tf file.jar:

    $ jar -tf resource-demo-0.0.1-SNAPSHOT.jar 
    META-INF/
    META-INF/MANIFEST.MF
    org/
    org/jamesd/
    org/jamesd/examples/
    org/jamesd/examples/resourcedemo/
    org/jamesd/examples/resourcedemo/images/
    org/jamesd/examples/resourcedemo/style/
    org/jamesd/examples/resourcedemo/fxml/
    org/jamesd/examples/resourcedemo/images/so-logo.png
    org/jamesd/examples/resourcedemo/style/main-style.css
    org/jamesd/examples/resourcedemo/Controller.class
    org/jamesd/examples/resourcedemo/fxml/MainView.fxml
    org/jamesd/examples/resourcedemo/App.class
    module-info.class
    META-INF/maven/
    META-INF/maven/org.jamesd.examples/
    META-INF/maven/org.jamesd.examples/resource-demo/
    META-INF/maven/org.jamesd.examples/resource-demo/pom.xml
    META-INF/maven/org.jamesd.examples/resource-demo/pom.properties
    $ 
    

    If the resources are not being deployed, or are being deployed to an unexpected location, check the configuration of your build tool or IDE.

    Example image loading troubleshooting code

    This code is deliberately more verbose than is strictly necessarily to facilitate adding additional debugging information for the image loading process. It also uses System.out rather than a logger for easier portability.

    String resourcePathString = "/img/wumpus.png";
    Image image = loadImage(resourcePathString);
    
    // ...
    
    private Image loadImage(String resourcePathString) {
        System.out.println("Attempting to load an image from the resourcePath: " + resourcePathString);
        URL resource = HelloApplication.class.getResource(resourcePathString);
        if (resource == null) {
            System.out.println("Resource does not exist: " + resourcePathString);
    
            return null;
        }
    
        String path = resource.toExternalForm();
        System.out.println("Image path: " + path);
    
        Image image = new Image(path);
        System.out.println("Image load error?  " + image.isError());
        System.out.println("Image load exception? " + image.getException());
    
        if (!image.isError()) {
            System.out.println("Successfully loaded an image from " + resourcePathString);
        }
    
        return image;
    }
    

    External Tutorial Reference

    A useful external tutorial for resource location is Eden coding's tutorial:

    The nice thing about the Eden coding tutorial is that it is comprehensive. In addition to covering the information on lookups from Java code which is in this question. The Eden tutorial covers topics such as locating resources that are encoded as urls in CSS, or resource references in FXML using an @ specifier or fx:include element (which are topics currently not directly covered in this answer).