0

I just started using Java 16 and can't seem to figure out why I am unable to access resource files. I went through some troubleshooting to narrow down at least where I seem to be having a problem.

I'm using IntelliJ IDEA 2021.2 Build #IU-212.4746.92

I created a new project and I chose JavaFX with OpenJDK 16.

enter image description here

It then creates the project with three main files, an Application class, a Controller class, and a FXML file. Once I create the project, I go into the POM file and I chose version 16 of javaFX-controls and javafx-fxml and I tell it to get the latest version of the other libraries it adds automatically into the POM file.

I also copy two folders from a different project into the resources folder - all copy and pasting is done within IntelliJ.

When I run the application that it put there (called HellpApplication), it works fine. And that application uses class.getResource to grab the fxml file and again ... it works just fine. However, when I try to run this class:

import java.io.*;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;

public class FileResourcesUtils {

    public static void main(String[] args) throws URISyntaxException {
        FileResourcesUtils app = new FileResourcesUtils();
        String fileName = "StyleSheets/AnchorPane.css";
        System.out.println("getResourceAsStream : " + fileName);
        InputStream is = app.getFileFromResourceAsStream(fileName);
        printInputStream(is);
        System.out.println("\ngetResource : " + fileName);
        File file = app.getFileFromResource(fileName);
        printFile(file);
    }

    private InputStream getFileFromResourceAsStream(String fileName) {
        ClassLoader classLoader = getClass().getClassLoader();
        InputStream inputStream = classLoader.getResourceAsStream(fileName);
        if (inputStream == null) {
            throw new IllegalArgumentException("file not found! " + fileName);
        }
        else return inputStream;
    }

    private File getFileFromResource(String fileName) throws URISyntaxException{

        ClassLoader classLoader = getClass().getClassLoader();
        URL resource = classLoader.getResource(fileName);
        if (resource == null) {
            throw new IllegalArgumentException("file not found! " + fileName);
        }
        else return new File(resource.toURI());}

    private static void printInputStream(InputStream is) {
        try (InputStreamReader streamReader =
                     new InputStreamReader(is, StandardCharsets.UTF_8);
                     BufferedReader reader = new BufferedReader(streamReader)
        ) 
        {
            String line;
            while ((line = reader.readLine()) != null) System.out.println(line);
        } 
        catch (IOException e) {e.printStackTrace();}
    }

    private static void printFile(File file) {
        List<String> lines;
        try {
            lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);
            lines.forEach(System.out::println);
        }
        catch (IOException e) {e.printStackTrace();}
    }

}

It throws this error:

getResourceAsStream : StyleSheets/AnchorPane.css
Exception in thread "main" java.lang.IllegalArgumentException: file not found! StyleSheets/AnchorPane.css
    at com.simtechdata.test/com.simtechdata.test.FileResourcesUtils.getFileFromResourceAsStream(FileResourcesUtils.java:27)
    at com.simtechdata.test/com.simtechdata.test.FileResourcesUtils.main(FileResourcesUtils.java:16)

Then, THE ONLY THING I HAVE TO DO ... is DELETE module-info.java, then that class runs perfectly! HOWEVER, without module-info.java there, I cannot run any FX code...

Here is module-info.java:

module com.simtechdata.test {
    requires javafx.controls;
    requires javafx.fxml;


    opens com.simtechdata.test to javafx.fxml;
    exports com.simtechdata.test;
}

Why did I use this class? Because I'm having the exact same problem trying to access resource files in my JavaFX application and this class was the easiest way for me to demonstrate the problem and show that it is specifically connected to the module-info file. When I run my own JavaFX code, it works just fine, until I try to access a resource file like a style sheet to throw onto a control.

What I would like to know is ... what sort of magic trick do I need to do in order to be able to access resource files from my JavaFX 16 application? What am I missing?

I'vr tried all of these different ways of getting to the resource file, but each one gives the same error:

String CSS_ANCHOR_PANE = this.getClass().getResource("StyleSheets/AnchorPane.css").toExternalForm();
String CSS_ANCHOR_PANE = ClassName.getClass().getResource("StyleSheets/AnchorPane.css").toExternalForm();

ClassLoader resource = ClassLoader.getSystemClassLoader();
String CSS_ANCHOR_PANE = resource.getResource("StyleSheets/AnchorPane.css").toExternalForm();

Class<?> resource = this.getClass();
String CSS_ANCHOR_PANE = resource.getResource("StyleSheets/AnchorPane.css").toExternalForm();

And here is a screenshot of the resource folder tree:

enter image description here

Any ideas?

Michael Sims
  • 2,360
  • 1
  • 16
  • 29
  • 1
    The error says that `FileResourcesUtils` is in `com.simtechdata.test` even though there is no package statement in the code you provided, so the code and error trace don't match. This means your getResource command will look for the resource in the location: `com/simtechdata/test/StyleSheets/AnchorPane.css`. But it is not there. If you want to get it relative the root directory, you need to put a `/` in front, e.g. `/StyleSheets/AnchorPane.css`. – jewelsea Aug 24 '21 at 17:52
  • 1
    Additionally, the linked question *does* provide a solution. Using `Class.getResource()` instead of `ClassLoader.getResource()` to avoid the modular issue, you would need an absolute path: `"/StyleSheets/AnchorPane.css"`. – James_D Aug 24 '21 at 19:01
  • @James_D When I try to use Class.getResource, the IDE says that the NON-static method getResource cannot be accessed from a static context. – Michael Sims Aug 24 '21 at 19:07
  • 1
    I meant that as a shorthand for the `getResource()` method defined in `Class`, as opposed to the `getResource()` method defined in `ClassLoader`. So `someReferenceToAnObjectOfTypeClass.getResource()`. E.g. `getClass().getResource()`, or `FileResourceUtils.class.getResource()`, etc. – James_D Aug 24 '21 at 19:08
  • 2
    @MichaelSims I voted to close as I believed it was a duplicate. I updated one of the answers which I flagged as a duplicate to include additional information specific to modularization, including the recommendation (and sample code snippets) to lookup resources directly via the class rather than the classloader. For now, I re-opened the question. – jewelsea Aug 24 '21 at 19:21
  • @James_D I tried the full class name with .class.getResource with just the String "StyleSheets/AnchorPane.css" then I tried walking up the folder tree one folder at a time in the fileName String and it simply is not working ... it has to be somehow connected to that module-info file and no one really addressed that issue. – Michael Sims Aug 24 '21 at 19:28
  • 2
    Link to updated potential duplicate answer: [How to reference javafx fxml files in resource folder?](https://stackoverflow.com/questions/19602727/how-to-reference-javafx-fxml-files-in-resource-folder). I know that answer refers to fxml, but this is really a resource location issue, so whether the resource being looked up is fxml or something else, really doesn't make a difference. – jewelsea Aug 24 '21 at 19:29
  • 2
    @MichaelSims All I can say is I tested this locally using the advice in the answers I linked previously and it worked for me. Did you fix the issue with the leading slash which I pointed out? From your comment, it would appear not. – jewelsea Aug 24 '21 at 19:31
  • @jewelsea - I don't see how you can call that a duplicate issue ... I am not having a problem with fxml or the fxmlLoader cause I don't even use FXML. – Michael Sims Aug 24 '21 at 19:32
  • @jewelsea - then why not leave my question open until someone answers it and helps me find an answer to the problem ... I'm not a Java expert by any stretch of the imagination so I come here to talk to people who can help me solve the problem. I did read your suggested duplicate post but I found no answers in there that solved my problem. I think there is something I don't understand in this situation that perhaps you are all taking for granted I keep saying it has something to do with that module file - which I have no clue what that file even does. – Michael Sims Aug 24 '21 at 19:35
  • 3
    The original duplicate post is titled "How do I determine the correct path for FXML files, **CSS files**, Images, **and other resources**. [My emphasis.] So it really does seem relevant, and really will fix your issue if you try it as posted there. E.g. you say in a comment *'I tried the full class name with .class.getResource with just the String "StyleSheets/AnchorPane.css"'*. But I said (in a comment and in my answer in that question) to use `"/StyleSheets/AnchorPane.css"`. – James_D Aug 24 '21 at 19:43
  • 2
    *" I keep saying it has something to do with that module file - which I have no clue what that file even does"*. See https://www.oracle.com/tr/corporate/features/understanding-java-9-modules.html – James_D Aug 24 '21 at 20:01
  • 1
    @MichaelSims As previously stated, the question has been reopened. – jewelsea Aug 24 '21 at 20:15
  • @James_D I tried "/StyleSheets/AnchorPane.css" ... I must have tried what felt like hundreds of ways to get at that damn resource file. I tried every class method that I know and I took that string and I tried it literally every conceivable way witth parent slashes, without parent slashes ... I tried with each parent folder walking up the file tree ... – Michael Sims Aug 25 '21 at 05:40
  • 2
    @MichaelSims Go through the “troubleshooting” section in the answer to the question I linked under “solution 1” below. In particular, check the contents of your build folder to make sure the resource is being deployed correctly. I definitely recommend renaming the package (containing the resource) to follow standard naming conventions. – James_D Aug 25 '21 at 10:55
  • @James_D I got it working. I just created a new project and with each change I made to it to get it structured the way I need it, I went one step at a time where I would make a change, then run it, and if it ran without error, I went on to the next change and now it's working the way I need it to. I ended up using your suggestion of ClassName.class.getResouorce which worked consistently this time. I'm not entirely sure what's different about this new project, but it is working. – Michael Sims Aug 25 '21 at 17:27
  • 1
    My first guess would be that in the original project, the CSS was not getting deployed for some reason, as suggested in the post recommending working through the "troubleshooting" section in the linked Q/A. – James_D Aug 25 '21 at 23:18
  • @James_D You have several times, pointed out to me that making statements from a position of emotional protest against the compiler, etc. is not acceptable, because it's not the compiler's fault for whatever issue is at hand. And on principle, I agree with you ... however, take a look at this ... and specifically see Edit #2 in my answer after understanding the problem: https://stackoverflow.com/questions/69041318 – Michael Sims Sep 15 '21 at 05:31

2 Answers2

4

This question is really answered in numerous other places around the StackOverflow site, but I'll pull together various solutions here.

You basically have three options:

  1. Use the getResource() (or getResourceAsStream(), though the former is slightly preferable) method defined in Class, instead of in ClassLoader with the correct path. This avoids any issues with modularity.
  2. Make the project non-modular (i.e. don't define a module-info.java file) and specify the modules to be added as runtime parameters to the JVM. This allows you to use either ClassLoader's getResource() or Class's getResource() method.
  3. Create a modular project and use ClassLoader's getResouce() method, and open the package containing the resource unconditionally.

Solution 1 is detailed here. Since the style sheet you are loading is in the StyleSheets package1, and the class you are loading it from appears (from the stack trace) to be in an unrelated package com.simtechdata.test, you should provide an absolute path:

String CSS_ANCHOR_PANE = this.getClass().getResource("/StyleSheets/AnchorPane.css").toExternalForm();

For solution 2, use

String CSS_ANCHOR_PANE = this.getClass().getClassLoader().getResource("StyleSheets/AnchorPane.css").toExternalForm();

Remove the module-info.java file, and run the application with the JVM arguments

--module-path="/path/to/javafx/modules" --add-modules="javafx.controls,javafx.fxml"

(Replace /path/to/javafx/modules with the actual file system path to the JavaFX modules. The javafx.fxml module is only required in the add-modules parameter if you are using FXML.)


For solution 3, use

String CSS_ANCHOR_PANE = this.getClass().getClassLoader().getResource("StyleSheets/AnchorPane.css").toExternalForm();

Since your resource is in the StyleSheets package1 you need to open that package unconditionally. Add the line

opens StyleSheets ;

to your module-info.java file. See https://stackoverflow.com/a/48790851/2189127


(1) It's very strongly advised to follow standard naming conventions; I'm not even sure if solution 3 will work with a non-standard package name (package names should be all lower-case).

James_D
  • 201,275
  • 16
  • 291
  • 322
  • 1
    FWIW, solution 1 worked for me, solution 2 should work, though I didn't try it. I wasn't able to get solution 3 (addition of an `opens` value in the `module-info.java`) to work, though I guess that is not that big of a deal as it isn't a recommended solution anyway. – jewelsea Aug 24 '21 at 21:02
  • 1
    I was able to get solution 3 to work. The issue was that I was placing a `/` in front of the resource name like I do with `class.getResource()` when looking up an absolute path. However, `class.getClassLoader().getResource()` doesn't require (or work with) a `/` prefix. The class loader-based lookup is already absolute and independent of the code location, so the `/` prefix is not needed. – jewelsea Aug 24 '21 at 22:02
  • @jewelsea I’m working from memory here, but I thought the docs said that a leading / in that circumstance would be ignored. – James_D Aug 24 '21 at 22:03
  • 2
    I don't think the [ClassLoader::getResource](https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java/lang/ClassLoader.html#getResource(java.lang.String)) document mentions what it would do with a leading slash. From my testing on Java 16 and looking at the source, the leading slash is ignored for other similar APIs, such as `class.getModule().getResourceAsStream()` but not in the default `class.getClassLoader().getResource()` method (for which the resource won't be found if you provide a leading `/`). – jewelsea Aug 24 '21 at 22:23
  • @jewelsea *arrgg you are right, the leading slash when looking up using classloader api _prevents_ the resource being found - would have sworn to remember that it doesn't matter ;) Note to self: re-read the api doc __always__ – kleopatra Aug 25 '21 at 10:46
  • 2
    @jewelsea Indeed, you're correct, and it's actually the opposite of what I (and apparently, @kleopatra) [remember](https://en.wikipedia.org/wiki/False_memory#Mandela_effect). Under "Using methods of `java.lang.ClassLoader`" [here](https://docs.oracle.com/javase/8/docs/technotes/guides/lang/resources.html), it's explicitly stated that *'The methods in ClassLoader use the given String as the name of the resource without applying any absolute/relative transformation (see the methods in Class). The name **should not have** a leading "/".'* [My emphasis]. – James_D Aug 25 '21 at 12:00
1
  1. You write a lot about JavaFX but instead you should concentrate on the real problem. As you have shown yourself via your test program this problem is a module system problem and not a JavaFX problem.

  2. You can get rid of this problem by just not using the module system. Just wrap your main method into something like this:

    class MyAppLauncher {public static void main(String[] args) {MyApp.main(args);}}

The assumption is that MyApp is your main class which extends Application. The use MyAppLauncher to start your JavaFX program and remove all module system related options.

mipa
  • 10,369
  • 2
  • 16
  • 35