0

Question

What should I do to be able to load resources in my JavaFX application when the application has been packaged as a Jar file?

Background

The application is a Maven project in IntelliJ IDEA. Running the application directly from IntelliJ works fine for all cases described below, but the jar file I build from the project mostly fails to load resources (images and a font).

Project Directory Tree

application
├── .idea
├── out
├── src
|   ├── main
|   |   ├── java
|   |   |   ├── META-INF
|   |   |   └── Main.java
|   |   └── resources
|   |       ├── images
|   |       |   ├── img1.jpg
|   |       |   └── img2.png
|   |       ├── fonts
|   |       |   └── font.ttf
|   |       └── stylesheet.css
|   └── test
├── target
├── application.iml
└── pom.xml

Loading Images works like this:

Image img1 = new Image(this.getClass().getResource("images/img1.jpg").toString());
Image img2 = new Image(this.getClass().getResource("images/img2.png"));

But since I actually want to load all images in the images folder without hard-coding their names I have been doing this:

File dir = new File(this.getClass().getResource("images").getFile());
List<File> imageFiles = new ArrayList<>(Arrays.asList(dir.listFiles()));
List<Image> images = new ArrayList<>();
for (int i = 0; i < imageFiles.size(); i++) {
   images.add(new Image("images/" + imageFiles.get(i).getName()));
}

This does not work when the application has been packaged into a jar file and results in a NullPointerException at line 2. It turns out that dir.listFiles() returns null and that dir.exists() returns false.

Loading a Font I have tried to do in two ways. Like this in stylesheet.css:

@font-face {
   font-family: 'Font';
   src: url('fonts/font.ttf');
}

Or like this as the first line in the start method of Main:

Font.loadFont(getClass().getResourceAsStream("fonts/font.ttf"), 20);

In either case I am applying the font through the stylesheet. Both cases work when running the application from within IntelliJ, but neither works when running the jar. The first method prints the error message

Jun 14, 2018 4:21:47 AM com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged
INFO: Could not load @font-face font [jar:file:/D:/path/in/file/system/java/application/out/artifacts/application_jar/application.jar!/fonts/font.ttf]

and the second method fails silently.

What I Have Done and Additional Information

I am building the jar in IntelliJ via Build -> Build Artifacts.... The resources directory is marked in IntelliJ as Resources Root and this is my pom.xml.

The generated jar file does contain all resources that I think it should have. That is, it has the following directory tree:

application.jar
├── images
|   ├── img1.jpg
|   └── img2.png
├── fonts
|   └── font.ttf
├── META-INF
├── Main.class
└── stylesheet.css

Resources I have consulted before making this post are Apache Maven Archiver, IntelliJ Working With Artifacts, StackOverflow NullPointerException When..., StackOverflow JavaFX and maven... and several similar questions. Many of them address the NullPointerException: Location is required issue, which seems to happen when loading a JavaFX layout from xml, which I am not doing, nor is that the error I am getting.

Note that I have virtually no previous experience with Maven, only a basic idea of file streams, class loaders etc. and some experience with JavaFX.

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • See https://stackoverflow.com/questions/11012819/how-can-i-get-a-resource-folder-from-inside-my-jar-file – Adisesha Jun 14 '18 at 04:18
  • Have you tried `this.getClass().getResource("yourpath").toExternalForm()`? – n247s Jun 14 '18 at 04:39
  • @Adi The accepted answer worked for loading images, thank you! I find it very weird that there does not seem to be a better way to load resources within a jar without knowing their names, however. – Ecen Cronzeton Jun 14 '18 at 19:29

1 Answers1

0

Two answers for this question.

Loading the font was just a typo on my part, I used wrong capitalization in the path name. Interestingly this typo only made the font not load when running the jar file, when running the app from IntelliJ it did not matter. Thus, you can load font from within a jar like this:

Font.loadFont(Memory.class.getResourceAsStream("fonts/Font.ttf"), 20);

Loading files, from within a jar without knowing their precise names seems to be impossible without a workaround. You have to get a reference to the jar file itself and enumerate over its entries. This is described in the answer to this question that Adi linked to in his comment.

As such, I was able to load my images like this:

List<String> imagePaths = getPaths("images");
List<Image> imagePool = new ArrayList<>();
for (int i = 0; i < imagePaths.size(); i++) {
   imagePool.add(new Image(imagePaths.get(i)));
}

where getPaths is

/** Returns the paths to all files in a folder. Useful for loading many files without knowing their names.
 * Importantly, this function will work whether or not the application is run from a jar or not.
 * However it might not work if the jar is not run locally.
 *
 * @param folderPath The relative path to the folder with no ending slash.
 * @return A List of path names to all files in that folder.
 * @throws IOException
 */
public static List<String> getPaths(String folderPath) throws IOException {
    final File jarFile = new File(Memory.class.getProtectionDomain().getCodeSource().getLocation().getPath());
    List<String> filePaths = new ArrayList<>();

    if(jarFile.isFile()) {  // Run with JAR file
        final JarFile jar = new JarFile(jarFile);
        final Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
        while(entries.hasMoreElements()) {
            final String name = entries.nextElement().getName();
            if (name.startsWith(folderPath + "/")) { //filter according to the folderPath
                filePaths.add(name);
            }
        }
        jar.close();
    } else { // Run with IDE
        final URL url = Memory.class.getResource("/" + folderPath);
        if (url != null) {
            try {
                final File apps = new File(url.toURI());
                for (File app : apps.listFiles()) {
                    filePaths.add(folderPath + "/" + app.getName());
                }
            } catch (URISyntaxException ex) {
                // never happens
            }
        }
    }
    return filePaths;
}

which is only very slightly altered from the answer to the question Adi linked to.