Accessing Resources from java, and in particular from a jar

Using a resources bundle is a way to externalize code dependencies from text, images or other type of resources. As far as I know, it was pioneered by the Mac OS design, back in 1984. It allows changing elements in an app (for instance graphics and text) without recompiling. It is uses to a large extent in Android, but since the Java  origin the functionality was there.

Accessing resources in Java is generally not difficult. However, doing it in a way that works when launching the App from inside the IDE (say Netbeans), from a command line using java and using java -jar may end up into troubles.

On Stackoverflow there are many questions regarding this issue, but I did not find yet a convincing and exhaustive response. One of teh clearest explanations, with a working demo, is presente by mykong on his web site. Unfortunately, it does not work in a jar…

So let’s try to clear the ground.

The first issue is: where should resources be placed?

They should be in your src directory, where the package directory for the source code is. In some IDE, the src directory contains a main directory which contains the root of the package: in that case also the resources directory should go there (see mykong example).

We show the file hierarchy for a Netbeans project:

FIle system view of the project

File system view of the project

In this case, the ResourceAccess java class is in package it.unitn.resourceaccess.

The resources we want to use are a file called text.txt, which will contain some text lines, and camera-photo.png which contain an image. We’ve put them into resources/file and resources/img into the src directory (the used names are arbitrary, as long as in our code we refer correctly to them).

The resulting Netbeans project view is the following:

Project tree in Netbeans

Project tree in Netbeans

Now let’s try to access these resources from our code using the Java API. There is the getResource() method in the Class class, which returns an URL, so it seems quite natural to use it:
URL u=getClass().getResource("/resources/file/test.txt");
File f=new File(u.getFile());

It works if we launch our main class using the command line “java it.unitn.resourceacces.ResourceAccess”, but sadly it does not work if we launch the jar (which we can find in the dist directory in our project): in such case the getFile method called on the URL returns null.

If we look at the URL, the problem is clear: it turns to be:

/path/to/the/jar/ResourceAccess.jar!resources/file/test.txt

The problem is and that the file we want to access is not visible in the file system, but is buried in the belly of the jar (the exclamation mark shows the separation), and so the getFile does not work.

The solution is to use a different approach:

InputStream in = getClass().getResourceAsStream("/resources/file/test.txt");

The problem is at this point how we access the stream. The solution, for a text file, is to use a Scanner.

But what if we want to use an image? We need to read it from the stream, and we can do it in this way:

InputStream in = getClass().getResourceAsStream("/resources/img/camera-photo.png");
image = ImageIO.read(in);

There is a similar, but slightly different option, by using the Class Loader. In this case, the path we give must be without the leading slash.

InputStream in = getClass().getClassLoader().getResourceAsStream("resources/img/camera-photo.png");

That’s all. So we can give a fully example, which works inside the IDE, from command line and also when launching the jar. The example shows both options (one of them is commented out).


package it.unitn.resourceaccess;
import java.awt.BorderLayout;
import java.awt.Image;
import java.io.IOException;
import java.io.InputStream;
import static java.lang.System.out;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class ResourceAccess {
public static void main(String[] args) {
ResourceAccess obj = new ResourceAccess();
}
ResourceAccess() {
printFile("resources/file/test.txt");
showImage("resources/img/camera-photo.png");
}
/** Access a resource file and print its content
*
* @param fileName qualified path, without leading /
*/
private void printFile(String fileName) {
StringBuilder result = new StringBuilder("");
Scanner scanner = null;
InputStream in = null;
//Option 1- with class loader
//in = getClass().getClassLoader().getResourceAsStream(fileName);
//Option 2 - without class loader
in = getClass().getResourceAsStream("/"+fileName);
scanner = new Scanner(in);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
result.append(line).append("\n");
}
scanner.close();
out.println(result.toString());
}
/** Access a resource image and show it
*
* @param fileName qualified path, without leading /
*/
private void showImage(final String imageName) {
InputStream in = null;
//Option 1 - with class loader
//in = getClass().getClassLoader().getResourceAsStream(imageName);
//Option 2 - without class loader
in = getClass().getResourceAsStream("/"+imageName);
Image image = null;
try {
image = ImageIO.read(in);
} catch (IOException ex) {
out.println("Caught exception "+ex);
System.exit(1);}
}
JFrame frame = new JFrame();
JLabel label = new JLabel(new ImageIcon(image));
frame.getContentPane().add(label, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
}

Advertisements
Posted in Java, Programming, Utilities

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: