Anytime one needs to load Java resources (XML, jpeg, PDF, etc) from the classpath, using Class.getResourceAsStream or ClassLoader.getResourceAsStream is the way to go. However, there seems to be some confusion on how each one of these methods deals with the path in URL. I hope that this detailed experiment will serve as a helpful reminder of what the requirements are for both methods.
To make things more interesting I’ve put together a little program that should demonstrate the results on practice. I’ve covered three scenarios for where the file may reside in a typical Java application.
Assume that directory structure looks like this:
/ /rootFile.txt /src/ /src/srcFile.txt /src/com/letor/example/ /src/com/letor/example/packageFile.txt
- rootFile.txt resides in the root folder
- srcFile.txt resides in the src folder, will be included in the classpath (alternative to this is your typical cls folder
- packageFile.txt lies in the same package directory as the class invoking the call
The program I wrote looks like this:
package com.letor.example;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ResourceLoader
{
private static Logger log_ = Logger.getLogger(ResourceLoader.class.getName());
public static String[] FILE_NAMES = { "rootFile.txt",
"/rootFile.txt",
"srcFile.txt",
"src/srcFile.txt",
"/src/srcFile.txt",
"packageFile.txt",
"com/letor/example/packageFile.txt",
"/com/letor/example/packageFile.txt" };
public static void main(String[] args)
{
StringBuilder sb = new StringBuilder();
sb.append("\n");
try
{
for (String filename : FILE_NAMES)
{
// Class
sb.append("Class\t\t");
InputStream in = ResourceLoader.class.getResourceAsStream(filename);
if (in == null)
{
sb.append("NO");
}
else
{
sb.append("YES");
in.close();
}
sb.append("\t[").append(filename).append("]\n");
// ClassLoader
sb.append("ClassLoader\t");
in = ClassLoader.getSystemResourceAsStream(filename);
if (in == null)
{
sb.append("NO");
}
else
{
sb.append("YES");
in.close();
}
sb.append("\t[").append(filename).append("]\n");
}
}
catch (IOException ioe)
{
log_.log(Level.SEVERE, "Failed to load a resource", ioe);
}
log_.info(sb.toString());
}
}
Running the program produces the following output. Class indicates Class.getResourceAsStream() call. ClassLoader indicates ClassLoader.getSystemResourceAsStream() method.
Class NO [rootFile.txt] ClassLoader NO [rootFile.txt] Class NO [/rootFile.txt] ClassLoader NO [/rootFile.txt] Class NO [srcFile.txt] ClassLoader YES [srcFile.txt] Class NO [src/srcFile.txt] ClassLoader NO [src/srcFile.txt] Class NO [/src/srcFile.txt] ClassLoader NO [/src/srcFile.txt] Class YES [packageFile.txt] ClassLoader NO [packageFile.txt] Class NO [com/letor/example/packageFile.txt] ClassLoader YES [com/letor/example/packageFile.txt] Class YES [/com/letor/example/packageFile.txt] ClassLoader NO [/com/letor/example/packageFile.txt]
Now let’s go over each one of the lines and figure out what is happening
Class NO [rootFile.txt] ClassLoader NO [rootFile.txt] Class NO [/rootFile.txt] ClassLoader NO [/rootFile.txt]
In all four cases files are not visible to the class loader since the root directory is not in the classpath.
Class NO [srcFile.txt] ClassLoader YES [srcFile.txt]
Class does not see this file since it looks in the same directory where it resides. ClassLoader, however starts at the root of the classpath and finds the file.
Class NO [src/srcFile.txt] ClassLoader NO [src/srcFile.txt] Class NO [/src/srcFile.txt] ClassLoader NO [/src/srcFile.txt]
In all of these cases the file is not found. Notice that prepending slash to the path does not help.
Class YES [packageFile.txt] ClassLoader NO [packageFile.txt]
This is the exact opposite of what we saw when looking for srcFile.txt. Here the Class finds the file since it is in the same package, but ClassLoader fails
Class NO [com/letor/example/packageFile.txt] ClassLoader YES [com/letor/example/packageFile.txt]
The last four cases are the most interesting. Notice the inverse relationship. Here the Class fails to find the file since it starts looking from its own directory. ClassLoader looks from the classpath root and easily finds the file
Class YES [/com/letor/example/packageFile.txt] ClassLoader NO [/com/letor/example/packageFile.txt]
Finally Class figures out what to do, since we’ve specified a slash. Now it knows to go back to the beginning of the classpath. ClassLoader is less lucky, that slash confuses it.
I hope that this has been as educational for you as it was for me. Let’s sum all of this up.
Class.getResourceAsStream() will always look in its own package directory first. The only way to tell it look elsewhere is to provide it with an absolute path
ClassLoader.getSystemResourceAsStream() will always start looking at the root of the classpath, but do not confuse it by a forward slash.