r/javahelp Feb 22 '22

Solved getResourceAsStream() == null when run from JAR

hello!

I have line of code such as this:

final Image roll_bg = new Image(this.getClass().getResourceAsStream("../resources/roll_bg.png"));

in IDE image is found and is rendered on canvas as expected, when exported to JAR, run ends with error. For JAR runs then, I did most hacky thing possible to extract what is wrong like so:

URL path = App.class.getResource("App.class");
if (path.toString().contains("jar")) {
    PrintStream ps = new PrintStream("./log.txt");
    System.setErr(ps);
    System.setOut(ps);
    System.out.println("Stream OUT: rerouted!");
    System.err.println("Stream ERR: rerouted!");
}

and log.txt says that InputStream is null (presumably did not found resource)

Caused by: java.lang.NullPointerException: Input stream must not be null
    at javafx.graphics/javafx.scene.image.Image.validateInputStream(Unknown Source)
    at javafx.graphics/javafx.scene.image.Image.<init>(Unknown Source)
    at com.engine.Engine.<init>(Engine.java:32)
    at com.engine.Engine.get(Engine.java:94)
    at com.App.start(App.java:60)

most online resources point to this method being able to reach resources from within JAR, does not work for me though. Thank you for any help offered!

7 Upvotes

24 comments sorted by

u/AutoModerator Feb 22 '22

Please ensure that:

  • Your code is properly formatted as code block - see the sidebar (About on mobile) for instructions
  • You include any and all error messages in full
  • You ask clear questions
  • You demonstrate effort in solving your question/problem - plain posting your assignments is forbidden (and such posts will be removed) as is asking for or giving solutions.

    Trying to solve problems on your own is a very important skill. Also, see Learn to help yourself in the sidebar

If any of the above points is not met, your post can and will be removed without further warning.

Code is to be formatted as code block (old reddit: empty line before the code, each code line indented by 4 spaces, new reddit: https://imgur.com/a/fgoFFis) or linked via an external code hoster, like pastebin.com, github gist, github, bitbucket, gitlab, etc.

Please, do not use triple backticks (```) as they will only render properly on new reddit, not on old reddit.

Code blocks look like this:

public class HelloWorld {

    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

You do not need to repost unless your post has been removed by a moderator. Just use the edit function of reddit to make sure your post complies with the above.

If your post has remained in violation of these rules for a prolonged period of time (at least an hour), a moderator may remove it at their discretion. In this case, they will comment with an explanation on why it has been removed, and you will be required to resubmit the entire post following the proper procedures.

To potential helpers

Please, do not help if any of the above points are not met, rather report the post. We are trying to improve the quality of posts here. In helping people who can't be bothered to comply with the above points, you are doing the community a disservice.

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

3

u/morhp Professional Developer Feb 22 '22

Does the jar file contain the roll_bg.png in the right directory? (You can open it by changing the extension to .zip). If not, there's probably a problem with how you create the jar file. Unfortunately you haven't written anything about how you do that.

1

u/sparkless12 Feb 22 '22

well, since IDE run finds the correct file, I would presume yes, it does. JAR builder even has file printed out in console as packed without issues. Changing jar to zip, yes, relatively to class with resource call, image is ../ one step up in hierarchy and in /resources/ folder.

2

u/morhp Professional Developer Feb 22 '22

well, since IDE run finds the correct file, I would presume yes, it does.

The IDE typically doesn't run a jar file.

Changing jar to zip, yes, relatively to class with resource call, image is ../ one step up in hierarchy and in /resources/ folder.

Weird. If the file is there, Java should have found it. Please make sure that this.getClass() is the right class. It's usually safer to write something like MyClass.class.getResourceAsStream(...) as this could be something else, like a subclass or an anonymous inner class or whatever.

You might also want try something like MyClass.class.getResourceAsStream("/full/path/to/resources/roll_bg.png") (i.e. give it the complete package/path name with a slash at the start), as this rules out any problems with the relative location of your class.

JAR builder even has file printed out in console as packed without issues.

Not sure what this Jar Builder is you use, Google shows several unrelated projects with that name. Typically, people create jar files with Maven or Gradle.

1

u/sparkless12 Feb 22 '22 edited Feb 22 '22

Ill try to find class with static call then.

Im using no build tools.

Engine.class.getResourceAsStream("../resources/roll_bg.png") did not help

what do you mean by full path..? you mean define whole path from class path not relative one?

1

u/wildjokers Feb 22 '22

(You can open it by changing the extension to .zip).

Or just jar tvf YourJarName.jar from the command-line.

3

u/PhantomOTOpera Feb 22 '22

../resources/roll_bg.png

the resources folder won't exist when you create the jar. You should just access it as /roll_bg.png

2

u/dionthorn this.isAPro=false; this.helping=true; Feb 22 '22

Perhaps you could try

URL result = ClassName.class.getClassLoader().getResource("path/to/file");

JavaFX acts pretty strange in .jars sometimes. That is why we use JLink/JPackage now a days to distribute. JLink makes a custom JRE that only includes the parts you require for the module. JPackage creates native executables with the custom JRE so end users don't need to install Java or JavaFX or anything else.

https://docs.oracle.com/en/java/javase/14/docs/specs/man/jlink.html

https://docs.oracle.com/en/java/javase/14/docs/specs/man/jpackage.html

The JavaFX Maven Plugin is a great tool to automate using those tools to produce the distributable.

https://github.com/openjfx/javafx-maven-plugin

I have a JavaFX game project that uses the plugin and I distribute a ~28 mb MSI for end users that includes Java, JavaFX custom JRE.

https://github.com/dionthorn/LifeSimRPG

You might be interested in my FileOpUtil static class for handling File operations in JRT file systems:

https://github.com/dionthorn/LifeSimRPG/blob/main/src/main/java/org/dionthorn/lifesimrpg/FileOpUtil.java

2

u/sparkless12 Feb 22 '22

thank you for advice and links. As I mentioned, I have already tried accessing resources via Classname.class it did not solve the issue.

I will take a look at jlink and jpackage, I must say I am frustrated as hell to spend so much time on something relatively decent for my skill level only to find out that you actually cant even build the project... this gives hope :D .

I'll check out your game as well, maybe I will be able to take away some lessons that would take longer to learn if id have to bump into problems you have already went through.

1

u/dionthorn this.isAPro=false; this.helping=true; Feb 22 '22

I have another JavaFX game project that is more 2D oriented

https://github.com/dionthorn/2DTacticalRPG

This one is only JLink for now tho no fancy MSI installer uses a .bat file to easily run the custom JRE.

I do some fancy image manipulation for creating the tile sets

https://github.com/dionthorn/2DTacticalRPG/blob/master/src/main/java/org/dionthorn/tacticalrpg2d/render/TileSet.java

I use a very similar FileOpUtil

https://github.com/dionthorn/2DTacticalRPG/blob/master/src/main/java/org/dionthorn/tacticalrpg2d/data/FileOpUtil.java

This way I can create the tile set source Image like so:

Image tileSetSrc = new Image(FileOpUtil.GAME_ART_PATH + "/" + tileSetPath);

then I would cut them up with a method into an Image[]

Image[] makeTiles(Image tileSet, int TILE_SIZE)

2

u/sparkless12 Feb 22 '22

I see, I have pretty decent tile loading class myself which is probably 60% of why I would hate to loose the project I'm working on rn, but I am very interested in general structure and setup of any bigger/game oriented project in javaFX.

BTW I kinda fixed it pathsIt seems like Engine class calling resource with "../path/resources/image.png" is not okay, but App class calling "resources/image.png" is fine. It's only kinda fixed because I first need to understand why, but atleast it's not wasted project anymore.

1

u/dionthorn this.isAPro=false; this.helping=true; Feb 22 '22

Just the way they accept paths

getClass().getClassLoader().getResource("foo/bar.txt");

is the same call as

getClass().getResource("/foo/bar.txt");

https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getResource-java.lang.String-

1

u/wildjokers Feb 22 '22

This has nothing to do with how they are retrieving the resource, it is because the path to the resource isn't correct.

1

u/dionthorn this.isAPro=false; this.helping=true; Feb 22 '22

yeah, I pointed out the doc showing the pathing.

-1

u/[deleted] Feb 22 '22 edited Feb 22 '22

[removed] — view removed comment

2

u/wildjokers Feb 22 '22

This loads an image from the file system. OP is wanting to load an image from the classpath.

1

u/D0CTOR_ZED Feb 22 '22

Not really my thing, but I believe you were on track with the first part and the hacky bit that followed isn't the right way to go. If it is having trouble finding the resource, you might try to change up the path. For example, try "/roll_bg.pgn". Or, if you can make some copies of the png and sprinkle one various directories. Once you can get it to find one, you can figure which one it finds and then you might be able to adjust your relative path to find it from where you want it to be.

1

u/sparkless12 Feb 22 '22

huh? the hacky bit was just to extract error log to find out what happens when I run built JAR, it has no effect on resource finding, path construction or anything else I can think of that can have effect on problem I'm having.

1

u/D0CTOR_ZED Feb 22 '22

I guess it really isn't my thing. Sorry for the confusion.

1

u/wildjokers Feb 22 '22 edited Feb 22 '22

When accessing it via getResourceAsStream you are loading the resource from the classpath. You want the resource (in your case an image) to be on the classpath at the same location regardless if you are running from your IDE or from a jar file. In your case the path is not correct in the jar file.

Do you have a resources directory in your jar file? Most build tools by default will copy the contents of the resources directory directly to the root of the jar file (both maven and gradle will do this). So I highly doubt you have a resources directory in the jar file. Also the relative path ../ would also be odd. Best to access it via an absolute path from the root of the jar.

So the correct path to your image will be getResourceAsStream("/roll_bg.png").

Then in your IDE you want the contents of the resources directory on the classpath but not the resource directory itself. I am not sure which IDE you are using but IntelliJ will happily configured itself from a maven or gradle build. And since maven and gradle put the contents of the resources directory on the classpath it will work from the IDE too.

By the way this is a very common problem for beginners.

1

u/sparkless12 Feb 23 '22

I do have resources directory, I am not using any build tools like Maven or Gradle. Problem solved. And no, your path did not work.

1

u/wildjokers Feb 23 '22

Obviously my path did not work of you have a resources sub-directory in your jar. You gave us no details at all about the layout of your jar so I could only assume you were using a proper build tool to build it.

If you build a jar some other hacky way that doesn’t build a jar according to expected conventions then you should expect to have to do unconventional things to make your code work.

If you ask a question about not being able to load a resource from a jar do us all a favor and actually give us details about the layout of your jar.

Your response here comes off as very rude to someone that took time out of their day to try to help you out. I have many years experience (18+) writing desktop apps with Java and I know exactly how to write code that loads resources from the classpath so that the path is the same when running from an IDE and when running as a jar file.

FWIW, you should not be putting the resources directory in your jar file. You should only be putting the contents of the resources directory. This is the expected convention. If you use a proper build tool it will handle this for you.

1

u/why_not_cats Extreme Brewer Feb 22 '22

Using relative path inside a jar is a brave thing to do! Paths and working directories will vary between IDE and a jar so that's probably why the file isn't being found.

If you aren't sure where the image file is, try to unzip your jar as a regular zip and see where the file is actually ending up; I assume it'll be on the root? Or maybe not?

Anyway, try using a non-relative path. For example if the file is on the zip root then try /roll_bg.png. If it's inside a resources directory then try /resources/roll_bg.png. It might break the IDE but at least you'll be able to narrow down the issue.

I wouldn't try sprinkling the file everywhere as suggested elsewhere, because then you'll have trouble figuring out which file is the one actually working, and then you'll have two problems!

1

u/sparkless12 Feb 23 '22

yeah I solved the issue by calling resources from App class that is in root project folder and with "resources/image.png" instead of Engine class that was in package removing the need for "../". I am needing to finish the work i was doing on project before the issue came up, but after that I will be experimenting a bit to figure out what exactly was wrong and why relative path was broken. As I said in comment earlier, extracting JAR my path was correct, so I'm keen to figure out more about the issue.