Search code examples
javajacksonclassloaderjava-web-startjnlp

JNLP class loading: Truncated Class File (not a maven cache issue!)


I have a project that consist of multiple jars, part of which is ours and the other part is 3rd party libraries. Application must be run via Java WebStart (JNLP). Twice in the meantime I encountered that if the 3rd party library is "too new", it fails to load with the following exception:

java.lang.ClassFormatError: Truncated class file
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at com.sun.jnlp.JNLPClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at com.sun.jnlp.JNLPClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at <...>.ApplicationImpl.<init>(ApplicationImpl.java:63)

Similar questions are often answered as "clear maven cache", but this is not related. Because if I run it as a regular java application (not WebStart), everything works just fine. But yes, I still tried it.

JDK versions I've tried for now is jdk1.8.0_151, jdk1.8.0_161.

My testing code looks like:

try {
  LOG.debug("@@@ jackson-core");
  Class.forName("com.fasterxml.jackson.core.Versioned");
  LOG.debug("@@@ jackson-annotations");
  Class.forName("com.fasterxml.jackson.annotation.JsonAutoDetect");
  LOG.debug("@@@ jackson-databind");
  Class.forName("com.fasterxml.jackson.databind.ObjectMapper");
} catch (ClassNotFoundException ex) {
  LOG.debug(ex);
}

So far I've tested this versions of libraries:

  • jackson-databind/core/annotations 2.5.0 (Jan, 2015) and later DOESN'T LOAD
  • jackson-databind/core/annotations 2.2.0 (Apr, 2013) LOADS
  • io.nats.jnats 1.0 (Feb, 2017) DOESN'T LOAD
  • io.nats.jnats 0.3.1 (Jan, 2016) LOADS
  • org.apache.httpcomponents.httpasyncclient 4.1 (Apr, 2015) LOADS
  • org.apache.httpcomponents.httpasyncclient 4.1.1 (Nov, 2015) and later DOESN'T LOAD

The project is compiled with target_jdk=8. Jars are signed with self-signed certificate, but I tested with production (official) certificate, problem remained. I've tried -XX:+TraceClassLoadinga and -verbose:class but it didn't work. Edit: moved it to separate question: Tracing JNLP class loading

And the most interesting part. If I repack this libraries into my own jars (jar-with-dependencies), they are loaded fine even via JNLP.

What could be the problem?


Solution

  • I figured it out. And the thing is marvelous. It turns out, that when built as a regular desktop Java app the jars indeed was different from those built as JNLP. A custom maven plugin was in place that rebuilt jars. And when it copied classes from JarInputStream to JarOutputStream, it used ZipEntry::size to get the amount of bytes to transfer. But as it turned out, this method is not guaranteed to report correct value (seriously, authors?). And it worked for older libraries, because the reported value was correct for them, but not always for newer ones.