When the RemoteClassLoader encounters a Java class file whose version is too high, it ends up throwing ClassFormatError.
This comes as a nasty surprise to software packages that expect to receive (and trap, and recover from) UnsupportedClassVersionError.
In particular, this breaks the JGit functionality in the git-client-plugin (versions 1.19.1 to 1.19.7) if it executes on a slave that's running JDK 1.6.
(Yes, I realize that JDK 1.6 is ancient history so this particular example may be moot now, but the same general problem may reappear in the long march from 1.7 to 1.8 or 1.8 to 1.9).
jgit-client-plugin 1.9.0 and below don't experience this problem because they didn't include the dependency that triggers this, org.eclipse.jgit:org.eclipse.jgit.java7. That dependency was added in git-client-plugin 1.19.1 with this checkin comment:
JENKINS-30371 Distribute JGit java7 jar to allow JGit symlink support
JGit symlink support is optional with JGit 3.7.1. It requires a copy of
the JGit java7 jar, a Java 7 VM, and a file system which supports symbolic
links. Including the jar file in a Java 6 environment is safe because
the JGit code won't load the additional jar when running on Java 6.
That last sentence is probably true in an environment where the RemoteClassLoader isn't involved (e.g. on master?) but definitely not on JDK 1.6 slaves.
There's nothing that the git-client-plugin can (or should) do to fix this; the code that's expecting to receive and handle UnsupportedClassVersionError is in org.eclipse.jgit:org.eclipse.jgit.
(I should probably say "was in" because newer versions of org.eclipse.jgit have made JDK 1.7 required rather than optional).
My (quite likely clueless) 0.02 re the code in RemoteClassLoader.loadClassFile() is roughly:
- In the case where it checks bytecodeLevel itself it should throw UnsupportedClassVersionError (not ClassFormatError)
- In the case where it wraps try-catch around the call to defineClass(), the current catch-block for ClassFormatError also catches the UnsupportedClassVersionError subtype and ends up throwing a ClassFormatError instead. It should, somehow or other, preserve the subtype (UnsupportedClassVersionError).
Perhaps that second path is never exercised now (because of the explicit bytecodeLevel check above)? I'm not sure. But I definitely got bitten by it today, admittedly using antique versions of Jenkins components (and JDK 1.6).
FWIW, when I encountered that second case, the UnsupportedClassVersionError that got caught and wrapped as ClassFormatError already displayed this:
java.lang.UnsupportedClassVersionError: org/eclipse/jgit/util/Java7FSFactory : Unsupported major.minor version 51.0
i.e. the catch-and-wrap-in-order-to-add-the-class-name rigmarole wasn't beneficial because the class name was already there. But I haven't checked whether that's always true.