How to list the files inside a JAR file?

Viewed 162872

I have this code which reads all the files from a directory.

    File textFolder = new File("text_directory");

    File [] texFiles = textFolder.listFiles( new FileFilter() {
           public boolean accept( File file ) {
               return file.getName().endsWith(".txt");
           }
    });

It works great. It fills the array with all the files that end with ".txt" from directory "text_directory".

How can I read the contents of a directory in a similar fashion within a JAR file?

So what I really want to do is, to list all the images inside my JAR file, so I can load them with:

ImageIO.read(this.getClass().getResource("CompanyLogo.png"));

(That one works because the "CompanyLogo" is "hardcoded" but the number of images inside the JAR file could be from 10 to 200 variable length.)

EDIT

So I guess my main problem would be: How to know the name of the JAR file where my main class lives?

Granted I could read it using java.util.Zip.

My Structure is like this:

They are like:

my.jar!/Main.class
my.jar!/Aux.class
my.jar!/Other.class
my.jar!/images/image01.png
my.jar!/images/image02a.png
my.jar!/images/imwge034.png
my.jar!/images/imagAe01q.png
my.jar!/META-INF/manifest 

Right now I'm able to load for instance "images/image01.png" using:

    ImageIO.read(this.getClass().getResource("images/image01.png));

But only because I know the file name, for the rest I have to load them dynamically.

17 Answers

I've ported acheron55's answer to Java 7 and closed the FileSystem object. This code works in IDE's, in jar files and in a jar inside a war on Tomcat 7; but note that it does not work in a jar inside a war on JBoss 7 (it gives FileSystemNotFoundException: Provider "vfs" not installed, see also this post). Furthermore, like the original code, it is not thread safe, as suggested by errr. For these reasons I have abandoned this solution; however, if you can accept these issues, here is my ready-made code:

import java.io.IOException;
import java.net.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;

public class ResourceWalker {

    public static void main(String[] args) throws URISyntaxException, IOException {
        URI uri = ResourceWalker.class.getResource("/resources").toURI();
        System.out.println("Starting from: " + uri);
        try (FileSystem fileSystem = (uri.getScheme().equals("jar") ? FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap()) : null)) {
            Path myPath = Paths.get(uri);
            Files.walkFileTree(myPath, new SimpleFileVisitor<Path>() { 
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    System.out.println(file);
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    }
}
public static ArrayList<String> listItems(String path) throws Exception{
    InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(path);
    byte[] b = new byte[in.available()];
    in.read(b);
    String data = new String(b);
    String[] s = data.split("\n");
    List<String> a = Arrays.asList(s);
    ArrayList<String> m = new ArrayList<>(a);
    return m;
}

Just to mention that if you are already using Spring, you can take advantage of the PathMatchingResourcePatternResolver.

For instance to get all the PNG files from a images folder in resources

ClassLoader cl = this.getClass().getClassLoader(); 
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(cl);
Resource[] resources = resolver.getResources("images/*.png");
for (Resource r: resources){
    logger.info(r.getFilename());
    // From your example
    // ImageIO.read(cl.getResource("images/" + r.getFilename()));
}

The most robust mechanism for listing all resources in the classpath is currently to use this pattern with ClassGraph, because it handles the widest possible array of classpath specification mechanisms, including the new JPMS module system. (I am the author of ClassGraph.)

How to know the name of the JAR file where my main class lives?

URI mainClasspathElementURI;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
        .enableClassInfo().scan()) {
    mainClasspathElementURI =
            scanResult.getClassInfo("x.y.z.MainClass").getClasspathElementURI();
}

How can I read the contents of a directory in a similar fashion within a JAR file?

List<String> classpathElementResourcePaths;
try (ScanResult scanResult = new ClassGraph().overrideClasspath(mainClasspathElementURI)
        .scan()) {
    classpathElementResourcePaths = scanResult.getAllResources().getPaths();
}

There are lots of other ways to deal with resources too.

One more for the road that's a bit more flexible for matching specific filenames because it uses wildcard globbing. In a functional style this could resemble:

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer;

import static java.nio.file.FileSystems.getDefault;
import static java.nio.file.FileSystems.newFileSystem;
import static java.util.Collections.emptyMap;

/**
 * Responsible for finding file resources.
 */
public class ResourceWalker {
  /**
   * Globbing pattern to match font names.
   */
  public static final String GLOB_FONTS = "**.{ttf,otf}";

  /**
   * @param directory The root directory to scan for files matching the glob.
   * @param c         The consumer function to call for each matching path
   *                  found.
   * @throws URISyntaxException Could not convert the resource to a URI.
   * @throws IOException        Could not walk the tree.
   */
  public static void walk(
    final String directory, final String glob, final Consumer<Path> c )
    throws URISyntaxException, IOException {
    final var resource = ResourceWalker.class.getResource( directory );
    final var matcher = getDefault().getPathMatcher( "glob:" + glob );

    if( resource != null ) {
      final var uri = resource.toURI();
      final Path path;
      FileSystem fs = null;

      if( "jar".equals( uri.getScheme() ) ) {
        fs = newFileSystem( uri, emptyMap() );
        path = fs.getPath( directory );
      }
      else {
        path = Paths.get( uri );
      }

      try( final var walk = Files.walk( path, 10 ) ) {
        for( final var it = walk.iterator(); it.hasNext(); ) {
          final Path p = it.next();
          if( matcher.matches( p ) ) {
            c.accept( p );
          }
        }
      } finally {
        if( fs != null ) { fs.close(); }
      }
    }
  }
}

Consider parameterizing the file extensions, left an exercise for the reader.

Be careful with Files.walk. According to the documentation:

This method must be used within a try-with-resources statement or similar control structure to ensure that the stream's open directories are closed promptly after the stream's operations have completed.

Likewise, newFileSystem must be closed, but not before the walker has had a chance to visit the file system paths.

Related