5

For some reason I keep getting an NPE in a gradle javafx project.

My folder structure is very basic. I have a package with my java files in the main/java folder. I also have my resources in the main/resources folder. When I try to load image.png it gives me an NPE.

public static Image createImage(Object context, String url) {
    Image m = null;
    InputStream str = null;
    URL _url = context.getClass().getResource(url);

    try {
        m = new Image(_url.getPath());
    } catch (NullPointerException e) {
        e.printStackTrace();
    }

    return m;
}

This is a helper class.

From the Scene I call: Image image = Helper.createImage(this, "image.png"); The absolute path to the image would be main/resources/images/image.png.

I checked every tutorial on the internet but I couldn't find any solution for this. I also tried it with the path to the image as parameter and also with an InputStream but it never worked.

Roy Berris
  • 1,502
  • 1
  • 17
  • 40
  • Method `getResource()` is searching for a file named _image.png_ in the package directory of `context`. – Abra Nov 25 '19 at 10:52
  • @Abra so how do I make him search in the `main/resources` dir? – Roy Berris Nov 25 '19 at 10:54
  • Easiest solution, in my opinion, is move file _image.png_ to the same directory as your class. Have you read the _javadoc_ for method [getResource](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getResource-java.lang.String-) ? – Abra Nov 25 '19 at 10:58
  • @Abra that is not an option. We need to place it in the resources directory – Roy Berris Nov 25 '19 at 11:04
  • What java version are you using? Is your application packaged in a JAR? – Abra Nov 25 '19 at 12:06
  • `getResource(String)` usually searches relative to the `resources` folder. So you should try `Helper.createImage(this, "images/image.png");`. Also [this question](https://stackoverflow.com/questions/15749192/how-do-i-load-a-file-from-resource-folder) could help. – Tobias Nov 25 '19 at 12:37

3 Answers3

9

Resources

The Class#getResource(String) and related API are used for locating resources relative to the class path and/or module path. When using Class to get a resource you can pass an absolute name or a relative name. An absolute name will locate the resource relative to the root of the class path/module path; an absolute name starts with a /. A relative name will locate the resource relative to the location of the Class; a relative name does not start with a leading /.

In a typical Maven/Gradle project structure, the src/main/java and src/main/resources are roots of the class path/module path. This means all resource names are relative to those directories. It's slightly more complicated than that because the files under those directories are moved to the target/build directory and it's that location that's put on the class path/module path, but for all intents and purposes consider the source directories as the root. There's a reason a get-resource API exists in the first place, to provide an application-location-independent way of obtaining resources.


Issues in Your Code

From your question I gather your project structure looks something like:

<project-dir>
|--src/
   |--main/
      |--java/
      |--resources/
         |--images/
            |--image.png

And you're calling your method with an Object and a resource name of image.png. The problem here is that, since you're passing a relative name, the resource is located relative to the Class of the passed Object (i.e. context). I doubt your class is located in a package named images which means the resource will not be found relative to said class. You need to pass an absolute name: /images/image.png.

The other problem is your use of URL#getPath(). The URL you obtain from Class#getResource(String) will, if the resource were to be found, look something like this:

file:/path/to/gradle/project/build/resources/main/images/image.png

But the result of URL#getPath() will give you:

/path/to/gradle/project/build/resources/main/images/image.png

This causes a problem due to the way Image works. From the documentation:

All URLs supported by URL can be passed to the constructor. If the passed string is not a valid URL, but a path instead, the Image is searched on the classpath in that case.

Notice the second sentence. If the passed URL does not have a scheme then it's interpreted as a resource name and the Image will locate the image file relative to the classpath. In other words, since you're passing the value of URL#getPath() to the Image constructor it searches for the resource image.png in the package path.to.gradle.project.build.resources.main.images. That package does not exist. You should be passing the URL as-is to the Image constructor via URL#toString() or URL#toExternalForm().


Solution

If you're going to use the URL returned by Class#getResource(String) to load the Image then no matter what you need to use URL#toString() or URL#toExternalForm() instead of URL#getPath().

public static Image createImage(Object context, String resourceName) {
  URL _url = context.getClass().getResource(resourceName);
  return new Image(_url.toExternalForm());
}

Then you have at least two options:

  1. Pass the absolute resource name (i.e. "/images/image.png") to your #createImage(Object,String) method since the image.png resource is not in the same package as the passed Object (i.e. context).

  2. Move the resource to the same package as the class of the passed in Object (i.e. context). For instance, if the context object's class is com.foo.MyObject then place the resource under src/main/resources/com/foo and it will be in the same package as MyObject. This will allow you to continue passing the relative resource name.

Of course, as noted by the documentation of Image you can pass a scheme-less URL and it's interpreted as a resource name. In other words, you could do:

Image image = new Image("images/image.png");

And that should work. A note of caution, however: When using modules the above will only work if the resource-containing package is opens unconditionally or if the module itself is open.

Slaw
  • 37,820
  • 8
  • 53
  • 80
  • Thanks a lot for your detailed answer. I am using in fact a `module`. Right now I am opening some parts of the application to javafx.controls. But how would I write it so that it is open to grab resources? Unfortunately above answer gives an IllegalArgumentException. – Roy Berris Nov 25 '19 at 14:59
  • For above comment: We don't want to open up the complete module if possible because we only want the database package to open up to our JDBC dependency. But it is also not convenient to put assets only in the package level because some assets might be used in different packages. – Roy Berris Nov 25 '19 at 15:07
  • In regards to resources, you only have to worry about `opens` directives if the resource is [encapsulated](https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/Module.html#getResourceAsStream(java.lang.String)). If you don't want to `opens` the resource's package then the best option is to pass the full, valid URL to the `Image` constructor as that will "bypass" the resource-locating mechanism and access the resource directly via the `URL`. The same goes for other JavaFX APIs that accept resource names, such as `[Scene|Parent]#getStylesheets()`. – Slaw Nov 25 '19 at 15:16
  • Personally, I recommend the full, valid URL approach when using modules. This is especially true when using Gradle since that tool, by default, [has issues with Java modules](https://stackoverflow.com/a/51921521/6395627). – Slaw Nov 25 '19 at 15:18
1

Try using the path /images/image.png.

The resources always get referenced from the class root, in your case src/main/resources, so from there going to /images/image.png should be the correct path.

AdminOfThis
  • 191
  • 3
  • If the answer works for you, pls remember to flag the answer as correct ;) – AdminOfThis Nov 25 '19 at 13:10
  • Should work. `Class.getResource("/...")` uses an absolute path (from the class path), whereas without preceding `/` it is a relative path to the actual class' package path. `x.y.z.C.class.getResoure("u/v/w.png")` would use `/x/y/z/u/v/w.png`. – Joop Eggen Nov 25 '19 at 13:11
0

this is how I am passing the images in my application. ivSerialAssignmentLogo is a FXML element (ImageView).

ivSerialAssignmentLogo.setImage(new Image(getClass().getResourceAsStream("/img/serialAssignment.svg")));

In your case, you could use something like that

public static Image createImage(Object context, String url) {
    Image m = null;
    InputStream str = null;
    URL _url = context.getClass().getResource("/images/" + url);

    try {
        m = new Image(_url.getPath());
    } catch (NullPointerException e) {
        e.printStackTrace();
    }

    return m;
}
WEGSY85
  • 291
  • 1
  • 3
  • 21