94

How can I use ClassLoader.getResources() to find recursivly resources from my classpath?

E.g.

  • finding all resources in the META-INF "directory": Imagine something like

    getClass().getClassLoader().getResources("META-INF")

    Unfortunately, this does only retrieve an URL to exactly this "directory".

  • all resources named bla.xml (recursivly)

    getClass().getClassLoader().getResources("bla.xml")

    But this returns an empty Enumeration.

And as a bonus question: How does ClassLoader.getResources() differ from ClassLoader.getResource()?

MRalwasser
  • 15,605
  • 15
  • 101
  • 147
  • 12
    @Andrew, a lot of frameworks iterate for some files based on name or extensions in the archives to automate some processes, like finding the ActionBeans of Stripes, or the hbm.xml files for hibernate. – bestsss Mar 04 '11 at 13:37
  • 2
    See http://stackoverflow.com/questions/1429172/how-do-i-list-the-files-inside-a-jar-file/ – Vadzim Aug 24 '12 at 15:29
  • The reson why it confuses you is that getResources works on a class loader which can have multiple JARs in the classpath. So if you have multiple JARs with the same resource, you get all. However it is NOT intended to search inside directories. With getResources("META-INF") you get all META-INFO directories in the search path of the CL and if the CL is a single jar file class loader, you at most get one entry. – eckes May 23 '14 at 14:05

5 Answers5

43

The Spring Framework has a class which allows to recursively search through the classpath:

PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
resolver.getResources("classpath*:some/package/name/**/*.xml");
rec
  • 10,340
  • 3
  • 29
  • 43
  • 1
    If you're trying resolve resources within a jar, and you're not sure what the pattern should look like, run `jar tf myjar.jar`. I ended up with `PathMatchinResourcePatternResolver("definitions/**/*.json")`. – MatrixManAtYrService Dec 15 '17 at 16:53
  • what does `classpath*` actually mean: is that intended to be replaced with the fully qualified contents of the current classpath to search in? – WestCoastProjects Jan 05 '20 at 12:10
  • If there is more than one JAR containing the same resource, then `classpath:my/resource.xml` is supposed to thrown an error while `classpath*:my/resource.xml` would return both. For more details, see e.g. http://tech-tauk.blogspot.com/2010/04/difference-between-classpath-classpath.html – rec Jan 06 '20 at 13:30
25

There is no way to recursively search through the classpath. You need to know the Full pathname of a resource to be able to retrieve it in this way. The resource may be in a directory in the file system or in a jar file so it is not as simple as performing a directory listing of "the classpath". You will need to provide the full path of the resource e.g. '/com/mypath/bla.xml'.

For your second question, getResource will return the first resource that matches the given resource name. The order that the class path is searched is given in the javadoc for getResource.

krock
  • 28,904
  • 13
  • 79
  • 85
  • 13
    *There is no way to recursively search through the classpath*... Sure, it's possible. For example: `URLClassLoader.getURLs()`. Open the JarFile, iterate through. – bestsss Mar 04 '11 at 13:04
  • 3
    @bestsss : what if the URL does not point to a JarFile, but to a directory? – MRalwasser Mar 04 '11 at 13:13
  • 6
    @bestsss, @MRalwasser: or even worse, if it points to a HTTP url? – Joachim Sauer Mar 04 '11 at 13:15
  • 4
    ;) well, the classloader can be just simple subclass of java.lang.ClassLoader w/o the URL part at any rate. It can generate the classes in the memory and so on. It might have very custom getResource, etc. @MRalwasser, if it points to some file system you process it like that, it's a simple scenario. @Joachim, besides applets nowadays there are not cases the files are kept remotely. The URL can be anything but as long as it is JAR one, you cant retrieve the jar from http.--What I told doesn't hold true always and it was not meant like universal solition,however it covers over 98% of the cases. – bestsss Mar 04 '11 at 13:32
23

This is the simplest wat to get the File object to which a certain URL object is pointing at:

File file=new File(url.toURI());

Now, for your concrete questions:

  • finding all resources in the META-INF "directory":

You can indeed get the File object pointing to this URL

Enumeration<URL> en=getClass().getClassLoader().getResources("META-INF");
if (en.hasMoreElements()) {
    URL metaInf=en.nextElement();
    File fileMetaInf=new File(metaInf.toURI());

    File[] files=fileMetaInf.listFiles();
    //or 
    String[] filenames=fileMetaInf.list();
}
  • all resources named bla.xml (recursivly)

In this case, you'll have to do some custom code. Here is a dummy example:

final List<File> foundFiles=new ArrayList<File>();

FileFilter customFilter=new FileFilter() {
    @Override
    public boolean accept(File pathname) {

        if(pathname.isDirectory()) {
            pathname.listFiles(this);
        }
        if(pathname.getName().endsWith("bla.xml")) {
            foundFiles.add(pathname);
            return true;
        }
        return false;
    }

};      
//rootFolder here represents a File Object pointing the root forlder of your search 
rootFolder.listFiles(customFilter);

When the code is run, you'll get all the found ocurrences at the foundFiles List.

Tomas Narros
  • 13,390
  • 2
  • 40
  • 56
  • 11
    I doubt that this will work in any cases for every type of ClassLoaders (e.g. classes within jar-files) – MRalwasser Mar 29 '11 at 08:04
  • 25
    I tested, I get an exception: java.lang.IllegalArgumentException: URI is not hierarchical. You cannot create a File object from an opaque URI like "jar:..." – cn1h Mar 19 '12 at 07:55
13

Here is code based on bestsss' answer:

    Enumeration<URL> en = getClass().getClassLoader().getResources(
            "META-INF");
    List<String> profiles = new ArrayList<>();
    while (en.hasMoreElements()) {
        URL url = en.nextElement();
        JarURLConnection urlcon = (JarURLConnection) (url.openConnection());
        try (JarFile jar = urlcon.getJarFile();) {
            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
                String entry = entries.nextElement().getName();
                System.out.println(entry);
            }
        }
    }
Mark Butler
  • 4,361
  • 2
  • 39
  • 39
  • 2
    The structure of this kind of confused me at first. It seems as though the only purpose of getClass().getClassLoader().getResources("META-INF") is to get a reference to some arbitrary file in the jar, which we then use to get the JarURLConnection. I originally thought this would iterate over files in the META-INF directory, but it actually iterates over every single file in the entire jar. – Alex Pritchard Nov 29 '13 at 19:10
  • It doesn't work for me. It shows only the contents of the first jar file `J` in the class path such that `J` contains an entry that starts with the `path` given to `getResources(path)` method. So, the result of your code snippet is incomplete and depends on the order of the elements in the class path. So, @krock was right when he said that _There is no way to recursively search through the classpath_ – Readren Apr 22 '16 at 23:58
  • Sorry for the necro comment, but this really does work. The only issue was that the "if" statement should be a "while". – Buddha Buddy Jan 21 '20 at 17:59
  • Does not seem to work with modern (Java 11) AppClassLoader - it will not iterate class path JARs. – alamar Sep 22 '22 at 17:58
5

MRalwasser, I'd give you a hint, cast the URL.getConnection() to JarURLConnection. Then use JarURLConnection.getJarFile() and voila! You have the JarFile and you are free to access the resources inside.

The rest I leave to you.

Hope this helps!

bestsss
  • 11,796
  • 3
  • 53
  • 63