125

Is there a way in Java to construct a File instance on a resource retrieved from a jar through the classloader?

My application uses some files from the jar (default) or from a filesystem directory specified at runtime (user input). I'm looking for a consistent way of
a) loading these files as a stream
b) listing the files in the user-defined directory or the directory in the jar respectively

Edit: Apparently, the ideal approach would be to stay away from java.io.File altogether. Is there a way to load a directory from the classpath and list its contents (files/entities contained in it)?

Mantrum
  • 1,405
  • 2
  • 11
  • 9

6 Answers6

104

I had the same problem and was able to use the following:

// Load the directory as a resource
URL dir_url = ClassLoader.getSystemResource(dir_path);
// Turn the resource into a File object
File dir = new File(dir_url.toURI());
// List the directory
String files = dir.list()
Chris Conway
  • 52,725
  • 40
  • 121
  • 150
  • 47
    This approach doesn't work with JARs in classpath, only with files and directories – yegor256 Oct 25 '11 at 14:58
  • 1
    @yegor256 If your file is in a jar you can create a `FileSystem` object and get the `Path` from that. Then you can read it like usual. (see http://stackoverflow.com/a/22605905/1876344) – mgttlinger Sep 17 '15 at 08:59
60

ClassLoader.getResourceAsStream and Class.getResourceAsStream are definitely the way to go for loading the resource data. However, I don't believe there's any way of "listing" the contents of an element of the classpath.

In some cases this may be simply impossible - for instance, a ClassLoader could generate data on the fly, based on what resource name it's asked for. If you look at the ClassLoader API (which is basically what the classpath mechanism works through) you'll see there isn't anything to do what you want.

If you know you've actually got a jar file, you could load that with ZipInputStream to find out what's available. It will mean you'll have different code for directories and jar files though.

One alternative, if the files are created separately first, is to include a sort of manifest file containing the list of available resources. Bundle that in the jar file or include it in the file system as a file, and load it before offering the user a choice of resources.

Jon Skeet
  • 1,261,211
  • 792
  • 8,724
  • 8,929
  • 3
    I agree it's annoying - but it makes ClassLoader more widely applicable in other ways. For instance, it's easy to write a "web classloader" because the web is good for fetching files, but it doesn't typically *list* files. – Jon Skeet Mar 24 '09 at 17:18
  • It seems like if the resource you are trying to load is much bigger than 1MiB the `InputStream` you get from `getResourceAsStream` stops to retrieve the bytes of the resource after that size and instead returns 0 iff it is contained in a compressed filesystem like a jar. You seem to be forced to use `getResource` and load the file independently from this in that case. – mgttlinger Sep 11 '15 at 14:45
  • 1
    @mgttlinger: That sounds unlikely to me... probably worth posting a new question with a repro if you can. – Jon Skeet Sep 11 '15 at 14:48
  • The issue was due to the `read` method deciding how much data to read (I wasn't aware of that). And it seems that the whole file is read if the file being read is located in a regular folder. If the file is within a jar because it has been packaged it reads only parts of it. – mgttlinger Sep 17 '15 at 08:51
  • 1
    @mgttlinger: No, it won't only read parts of it - but it might not fill the whole buffer provided on every read call. As always with `InputStream`, if you want to read the whole resource, you should loop until `read` returns -1. – Jon Skeet Sep 17 '15 at 08:54
53

Here is a bit of code from one of my applications... Let me know if it suits your needs. You can use this if you know the file you want to use.

URL defaultImage = ClassA.class.getResource("/packageA/subPackage/image-name.png");
File imageFile = new File(defaultImage.toURI());

Hope that helps.

Maurice Rogers
  • 547
  • 4
  • 2
12

A reliable way to construct a File instance on a resource retrieved from a jar is it to copy the resource as a stream into a temporary File (the temp file will be deleted when the JVM exits):

public static File getResourceAsFile(String resourcePath) {
    try {
        InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath);
        if (in == null) {
            return null;
        }

        File tempFile = File.createTempFile(String.valueOf(in.hashCode()), ".tmp");
        tempFile.deleteOnExit();

        try (FileOutputStream out = new FileOutputStream(tempFile)) {
            //copy stream
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
        }
        return tempFile;
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}
Lukas Masuch
  • 356
  • 3
  • 8
4

Try this:

ClassLoader.getResourceAsStream ("some/pkg/resource.properties");

There are more methods available, e.g. see here: http://www.javaworld.com/javaworld/javaqa/2003-08/01-qa-0808-property.html

topchef
  • 17,019
  • 8
  • 58
  • 98
  • Thanks for your response. I know about getResourceAsStream, but I don't think I can load a directory from the classpath through that. – Mantrum Mar 24 '09 at 04:02
  • In Java directory is a file (UNIX roots I guess) so you should at least try. – topchef Mar 24 '09 at 04:25
  • I think to list files in a directory you are going to need to use java.io.File. You can look up files with the ClassLoader.findResource which returns a URL that can be passed to File. – Nathan Voxland Mar 24 '09 at 04:31
2

This is one option: http://www.uofr.net/~greg/java/get-resource-listing.html

Fish
  • 21
  • 1
  • 5
    Whilst this link may answer the question, [it would be preferable](//meta.stackoverflow.com/q/8259) to include the essential parts of the answer here, and provide the link for reference, as a link may change or the linked page may be removed, thereby making the answer useless. – JonasCz Apr 30 '15 at 19:04