I am currently migrating an app from Java 8 to Java 11 via Java 9. The app uses a custom executable war framework and makes use of nested class resolution code from here: http://pavel.savara.sweb.cz/files/JarJarURLConnection.java. This code allows the bootstrap code in the executable war to load classes from jars in its own archive using urls of the form:
jar:jarjar:file:<path_to_war>^/WEB-INF/<path_to_jar>!/<path_to_class>
The intent is that the initial jar
protocol causes the JRE to treat jarjar:file:<path_to_war>^/WEB-INF/<path_to_jar>
as a resource to load and then anything after !/
is the path of the class to be loaded.
The custom jarjar
protocol handler parses the requested url jarjar:file:<path_to_war>^/WEB-INF/<path_to_jar>
and replaces it with another jar
protocol url: jar:file:<path_to_war>!/WEB-INF/<path_to_jar>
(note the switch from ^
to !
).
This works fine up to Java 8 but fails from Java 9 onwards and I'm not sure why. The JRE seems to ignore the transformed url created by parseURL
and attempts to use the plain war location without the nested jar from WEB-INF.
Any help much appreciated. This code is part of some other more complex code so we'd prefer not have to rework the entire thing.
EDIT: Github repository with basic code and a readme describing difference in behaviour between Java 8 and Java 11...
https://github.com/jugglingcats/execwar-test
EDIT2:
Running both in debugger I can see that the path is quite different, and on JDK11 a url with double !
is produced which breaks everything.
In JDK8 when parseUrl is called the stack looks like this:
parseURL:111, JarJarURLConnection$JarJarURLStreamHandler (com.acme)
<init>:639, URL (java.net)
<init>:507, URL (java.net)
<init>:456, URL (java.net)
parseSpecs:175, JarURLConnection (java.net)
<init>:158, JarURLConnection (java.net)
<init>:81, JarURLConnection (sun.net.www.protocol.jar)
openConnection:41, Handler (sun.net.www.protocol.jar)
openConnection:1001, URL (java.net)
findResource:715, URLClassPath$Loader (sun.misc)
findResource:225, URLClassPath (sun.misc)
run:572, URLClassLoader$2 (java.net)
run:570, URLClassLoader$2 (java.net)
doPrivileged:-1, AccessController (java.security)
findResource:569, URLClassLoader (java.net)
getResource:1089, ClassLoader (java.lang)
getResourceAsStream:233, URLClassLoader (java.net)
main:23, Main (com.acme)
You can see we are already in openConnection
.
In JDK11 it looks like:
parseURL:111, JarJarURLConnection$JarJarURLStreamHandler (com.acme)
<init>:674, URL (java.net)
<init>:541, URL (java.net)
<init>:488, URL (java.net)
run:487, URLClassPath$3 (jdk.internal.loader)
run:476, URLClassPath$3 (jdk.internal.loader)
doPrivileged:-1, AccessController (java.security)
getLoader:475, URLClassPath (jdk.internal.loader)
getLoader:444, URLClassPath (jdk.internal.loader)
findResource:290, URLClassPath (jdk.internal.loader)
run:655, URLClassLoader$2 (java.net)
run:653, URLClassLoader$2 (java.net)
doPrivileged:-1, AccessController (java.security)
findResource:652, URLClassLoader (java.net)
getResource:1400, ClassLoader (java.lang)
getResourceAsStream:322, URLClassLoader (java.net)
main:23, Main (com.acme)
Issue seems to be in these two lines (at URLClassPath:487):
URL nestedUrl = new URL(file.substring(0, file.length() - 2));
return new JarLoader(nestedUrl, jarHandler, lmap, acc);
The JarLoader
constructor creates a new jar url like so:
JarLoader(URL url, URLStreamHandler jarHandler,
HashMap<String, Loader> loaderMap,
AccessControlContext acc)
throws IOException
{
super(new URL("jar", "", -1, url + "!/", jarHandler));
csu = url;
handler = jarHandler;
lmap = loaderMap;
this.acc = acc;
ensureOpen();
}
The JarLoader.base
property now has value jar:jarjar:jar:file:webapp.war!/lib.jar!/
which is going to break things.