JDK-8150615 : URLClassLoader.getResources only returns one match from multiple JAR files mentioned in an JAR index
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.net
  • Affected Version: 8u72,9
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2016-02-24
  • Updated: 2018-09-11
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Description
FULL PRODUCT VERSION :
java version "1.8.0_72"
Java(TM) SE Runtime Environment (build 1.8.0_72-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.72-b15, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
10.11.3 (15D21)

A DESCRIPTION OF THE PROBLEM :
When there is an indexed JAR file in the class path, then a call to URLClassLoader.getResources() will only return one matching resource from all the JAR files mentioned in that index. This is in contrast to the specification of getResources, which claims that it ���finds all the resources with the given name.���

I had a look at the OpenJDK sources. The relevant class here is sun.misc.URLClassPath. It contains a (lazily constructed) list of loaders, and queries each loader in turn to assemble its result. However, if a JAR file contains an index, then the JAR files therein will explicitely be excluded from getting added to the list of loaders. Instead, the loader for the JAR containing the index will query said index for the name in question, and traversed the resulting list. But here is the catch: this happens in a method URLClassPath$JarLoader.getResource which returns a single Resource object. It is not possible for this method to return multiple resources. And as all objects in the index are handled by a single loader, a single resource is all we get.

I guess one would have to introduce a getResources method at that level as well. Or to represent the dependency jar files as loaders in their own right, in such a way that these loaders won't open their file intil doing so becomes neccessary. They probably would share the index and make use of that to decide when to open a given JAR file.

Cross reference: I asked about this behavior on Stack Overflow, at http://stackoverflow.com/q/35590606/1468366.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
 1. Write the source code below to TryIt.java
 2. javac TryIt.java
 3. mkdir a/foo b/foo
 4. touch a/foo/arb a/foo/bar b/foo/bar b/foo/cab
 5. echo "Class-Path: b.jar" > mf
 6. jar cfm a.jar mf -C a foo
 7. jar cf b.jar -C b foo
 8. java TryIt
 9. jar -i a.jar
10. java TryIt

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I'd expect both runs to have the same result I get for the first (unindexed) run:

'foo':
 jar:file:���/a.jar!/foo
 jar:file:���/b.jar!/foo
'foo/':
 jar:file:���/a.jar!/foo/
 jar:file:���/b.jar!/foo/
'foo/arb':
 jar:file:���/a.jar!/foo/arb
'foo/bar':
 jar:file:���/a.jar!/foo/bar
 jar:file:���/b.jar!/foo/bar
'foo/cab':
 jar:file:���/b.jar!/foo/cab
ACTUAL -
In the second run there is only one matching URL for each resource name:

'foo':
 jar:file:���/a.jar!/foo
'foo/':
 jar:file:���/a.jar!/foo/
'foo/arb':
 jar:file:���/a.jar!/foo/arb
'foo/bar':
 jar:file:���/a.jar!/foo/bar
'foo/cab':
 jar:file:���/b.jar!/foo/cab

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.*;
import java.net.*;
import java.util.*;

public class TryIt {
    public static void main(String[] args) throws Exception {
        URL[] urls = {
            (new File("a.jar")).getAbsoluteFile().toURI().toURL(),
            (new File("b.jar")).getAbsoluteFile().toURI().toURL()
        };
        URLClassLoader cl = URLClassLoader.newInstance(urls);
        String[] res = { "foo", "foo/", "foo/arb", "foo/bar", "foo/cab" };
        for (String r: res) {
            System.out.println("'" + r + "':");
            for (URL u: Collections.list(cl.getResources(r)))
                System.out.println(" " + u);
        }
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Possible workarounds:
* disable jar file indexing
* write own code to traverse the class path and inspect all the jar files therein
* write own code to access jar index and inspect matching jar files only

All of these come at a performance cost. Not needing multiple resources might be a viable workaround in some applications, but not in others.


Comments
Jar Indexing is not all that popularly used. Within this, splitting of "packages" across multiple jar files is somewhat of a corner case ( and not such a good idea ( not something that we want to actively encourage ) ). For these reasons I think this is a P4 ( I:M, L:M, W:L )
24-01-2017

Tried the provided steps to reproduce on : JDK 8u74 - Fail JDK 9ea -Fail
25-02-2016