Java classloader not able to find resource in jar file - java

I have a runnable jar file which is not able to access my resources which reside outside of the default src directory. Based on my understanding from What is the difference between Class.getResource() and ClassLoader.getResource(), I should be able to access root/res/img/img1.png (see folder setup below) by using the following getResourceFile function:
public class Foo {
private static final ClassLoader CLASS_LOADER = Foo.class.getClassLoader();
public static File getResourceFile(String relativePath) {
// Since I'm using getClassLoader, the path will resolve starting from
// the root of the classpath and it'll take an absolute resource name
// usage: getResourceFile("img/img1.png")
// result: Exception in thread "main" java.lang.NullPointerException
return new File(CLASS_LOADER.getResource(relativePath).getFile());
}
}
folder setup:
root/
src/
foo/
bar/
res/
img/
img1.png
audio/
audio1.wav
The problem arises when I try to execute the jar executable itself. However, the strange thing is that I was not able to replicate this through eclipse IDE which was actually able to resolve the path correctly. I have added the resource directory to the build path via (Project -> Properties -> Java Build Path -> Add Folder) so Java should be able to find the resource folder at runtime.
Is there something I'm missing in terms of generating the jar file? When unpacking the jar file everything seems to be in order with the img and audio directories being in the root (given the above initial folder setup):
foo/
/bar
img/
img1.png
audio/
audio1.wav

Files can only be used to represent actual files in your filesystem. And once you package your files into a JAR, the resource (img/img1.png) is not a file anymore, but an entry in the JAR file. As long as you use the folder structure from within Eclipse, the resources are individual files so everything is fine.
Try this:
System.out.println(CLASS_LOADER.getResource(relativePath));
It will print a URL, but it will not be a valid path to a file in your file system, but to an entry within the JAR file.
Usually, you will only want to read a resource. In that case, use getResourceAsStream() to open an InputStream.

Related

getResource will always return null

This is driving me crazy. I have a simple eclipse project with a src folder and a class in it. But I can't seem to get getResource to find it. What am I doing wrong?
import java.net.URL;
public class ContextTest {
public static void main(String[] args) {
URL url = ContextTest.class.getResource("/src/ContextTest.java");
System.out.println(url);
}
}
If I right-click on the class name, the path is /TestsProject/src/ContextTest.java and the default classpath according to the Classpath tab in Run Configurations is TestProject.
It doesn't work with /bin/ContextTest.java , /ContextTest.java , ContextTest.java either.
When you load resources using ContextTest.class.getResource("/....") the leading / is translated as an absolute path. Here absolute means from your root package (i.e. the default package).
In Eclipse the root package is considered the one that is under the src folder. Your compiled classes will be placed under bin folder and if you create a jar you will see that your root package is not the src or bin folders but whatever folders are inside it. (for example com).
So the correct way to load a resource using a class absolute path would be ContextTest.class.getResource("/ContextTest.java");. If the file ContextTest.java is in the root package of wherever your compiled classes are, then it will be found and returned.
I hope this clears the picture.
Update: From the comments below it is not clear what you are trying to do. When you use getResource() you are not loading a file but a resource from the classpath. This would correctly find the resource even if your files were inside a jar file. So for your above example to work the file you are trying to load as a resource should be in the classpath (i.e. under bin folder since this is the root of your classpath when you execute from inside Eclipse). If you are trying to load a file outside of your classpath then don't try to load a resource, you could use File instead.
Resources accessed via getResource() must be on the classpath. Your Java files will be compiled and placed on the classpath. The compiled .java file will be given an extension of .class.
Try
URL url = ContextTest.class.getResource("ContextTest.class");

Relative paths to getResourceAsStream method

I have a jav aproject which is build through ant. It write the class files to output/classes/com/... path. One of my java classes needs input stream read from a file that is in a folder one level above output folder. Looks like if copy the file to the package folder under outptu/classes, it seems to work. But I do not want to palce my config file in output folder as it will be cleaned when I do ant clean. I want it to find it look above the output folder, in config folder and load it.
public static final String CONFIG_FILE="/../../../../../../../Config.txt";
public static ConfigObj getConfigObj() throws IOException {
InputStream i=ConfigLoader.class.getResourceAsStream(CONFIG_FILE);
...
I want to know when I want to give raltivepath, what should it be relative to. I tried looking up , it says relative to classloader. What is classloader in this case? Is it output/classes/com....../config folder where my ConfigLoader.class lives?
The problem is that getResourceAsStream() will only load resources from the classpath. I guess you only have output/classes on your classpath, so you will never be able to load the config file via getResourceAsStream() if it's outside that directory. Use a File with an absolut path pointing to the file, or place it in your classpath.

Java JAR: Writing to a file

Currently, in my eclipse project, I have a file that I write to. However, I have exported my project to a JAR file and writing to that directory no longer works. I know I need to treat this file as a classpath resource, but how do I do this with a BufferedWriter?
You shouldn't have to treat it as a classpath resource to write to a file. You would only have to do that if the file was in your JAR file, but you don't want to write to a file contained within your JAR file do you?
You should still be able to create and write to a file but it will probably be relative to the working directory - the directory you execute your JAR file from (unless you use an absolute path). In eclipse, configure the working directory from within the run configuration dialog.
You're probably working in Linux. Because, in Linux, when you start your application from a JAR, the working directory is set to your home folder (/home/yourname/). When you start it from Eclipse, the working directory is set to the project folder.
To make sure you really know the files you are using are located in the project folder, or the folder where your JAR is in, you can use this piece of code to know where the JAR is located, then use the File(File parent, String name) constructor to create your files:
// Find out where the JAR is:
String path = YourClass.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
path = path.substring(0, path.lastIndexOf('/')+1);
// Create the project-folder-file:
File root = new File(path);
And, from now on, you can create all your File's like this:
File myFile = new File(root, "config.xml");
Of course, root has to be in your scope.
Such resources (when altered) are best stored in a sub-directory of user.home. It is a reproducible path that the user should have write access to. You might use the package name of the main class as a basis for the sub-directory. E.G.
our.com.Main -> ${user.home}/our/com/

How do I reference a resource in Java?

I need to read a file in my code. It physically resides here:
C:\eclipseWorkspace\ProjectA\src\com\company\somePackage\MyFile.txt
I've put it in a source package so that when I create a runnable jar file (Export->Runnable JAR file) it gets included in the jar. Originally I had it in the project root (and also tried a normal sub folder), but the export wasn't including it in the jar.
If in my code I do:
File myFile = new File("com\\company\\somePackage\\MyFile.txt");
the jar file correctly locates the file, but running locally (Run As->Java Main application) throws a file not found exception because it expects it to be:
File myFile = new File("src\\com\\company\\somePackage\\MyFile.txt");
But this fails in my jar file. So my question is, how do I make this concept work for both running locally and in my jar file?
Use ClassLoader.getResourceAsStream or Class.getResourceAsStream. The main difference between the two is that the ClassLoader version always uses an "absolute" path (within the jar file or whatever) whereas the Class version is relative to the class itself, unless you prefix the path with /.
So if you have a class com.company.somePackage.SomeClass and com.company.other.AnyClass (within the same classloader as the resource) you could use:
SomeClass.class.getResourceAsStream("MyFile.txt")
or
AnyClass.class.getClassLoader()
.getResourceAsStream("com/company/somePackage/MyFile.txt");
or
AnyClass.class.getResourceAsStream("/com/company/somePackage/MyFile.txt");
If I have placed i file in a jar file, it only worked if and only if I used
...getResourceAsStream("com/company/somePackage/MyFile.txt")
If I used a File object it never worked. I got also the FileNotFound exception. Now, I stay with the InputStream object.

Absolute Path of Project's folder in Java

Lots of confusion in this topic. Several Questions have been asked. Things still seem unclear.
ClassLoader, Absolute File Paths etc etc
Suppose I have a project directory structure as,
MyProject--
--dist
--lib
--src
--test
I have a resource say "txtfile.txt" in "lib/txt" directory. I want to access it in a system independent way. I need the absolute path of the project.
So I can code the path as abspath+"/lib/Dictionary/txtfile.txt"
Suppose I do this
java.io.File file = new java.io.File(""); //Dummy file
String abspath=file.getAbsolutePath();
I get the current working directory which is not necessarily project root.
Suppose I execute the final 'prj.jar' from the 'dist' folder which also contains "lib/txt/txtfile.txt" directory structure and resource,It should work here too. I should absolute path of dist folder.
Hope the problem is clear.
You should really be using getResource() or getResourceAsStream() using your class loader for this sort of thing. In particular, these methods use your ClassLoader to determine the search context for resources within your project.
Specify something like getClass().getResource("lib/txtfile.txt") in order to pick up the text file.
To clarify: instead of thinking about how to get the path of the resource you ought to be thinking about getting the resource -- in this case a file in a directory somewhere (possibly inside your JAR). It's not necessary to know some absolute path in this case, only some URL to get at the file, and the ClassLoader will return this URL for you. If you want to open a stream to the file you can do this directly without messing around with a URL using getResourceAsStream.
The resources you're trying to access through the ClassLoader need to be on the Class-Path (configured in the Manifest of your JAR file). This is critical! The ClassLoader uses the Class-Path to find the resources, so if you don't provide enough context in the Class-Path it won't be able to find anything. If you add . the ClassLoader should resolve anything inside or outside of the JAR depending on how you refer to the resource, though you can certainly be more specific.
Referring to the resource prefixed with a . will cause the ClassLoader to also look for files outside of the JAR, while not prefixing the resource path with a period will direct the ClassLoader to look only inside the JAR file.
That means if you have some file inside the JAR in a directory lib with name foo.txt and you want to get the resource then you'd run getResource("lib/foo.txt");
If the same resource were outside the JAR you'd run getResource("./lib/foo.txt");
First, make sure the lib directory is in your classpath. You can do this by adding the command line parameter in your startup script:
$JAVA_HOME/bin/java -classpath .:lib com.example.MyMainClass
save this as MyProject/start.sh or any os dependent script.
Then you can access the textfile.txt (as rightly mentioned by Mark) as:
// if you want this as a File
URL res = getClass().getClassLoader().getResource("text/textfile.txt");
File f = new File(res.getFile());
// As InputStream
InputStream in = getClass().getClassLoader()
.getResourceAsStream("text/textfile.txt");
#Mark is correct. That is by far the simplest and most robust approach.
However, if you really have to have a File, then your best bet is to try the following:
turn the contents of the System property "java.class.path" into a list of pathnames,
identify the JAR pathname in the list based on its filename,
figure out what "../.." is relative to the JAR pathname to give you the "project" directory, and
build your target path relative to the project directory.
Another alternative is to embed the project directory name in a wrapper script and set it as a system property using a -D option. It is also possible to have a wrapper script figure out its own absolute pathname; e.g. using whence.

Categories