Classpath issues when using ANT tasks with Java 11


(Christian Ciach) #1

I am in the progress of migrating from Java 8 to Java 11.

There is one ANT task in our build that we need to create our database schema. The ANT task (org.hibernate.tool.hbm2ddl.SchemaExportTask) is part of the Hibernate ORM core library. The task depends on JAXB, which was part of the JDK before but was removed with Java 11.

Fortunately, the JAXB runtime is available as an external library, so let’s just add it to the ANT task classpath:

configurations {
  hibtools
}

dependencies {
  hibtools group: 'org.glassfish.jaxb', name: 'jaxb-runtime', version: '2.3.1'
  hibtools group: 'org.hibernate', name: 'hibernate-core', version: '5.3.6.Final'
}

task createSchema {
  doLast {
    ant.taskdef(name: 'schemaexport', 
        classname: 'org.hibernate.tool.hbm2ddl.SchemaExportTask',
        classpath: configurations.hibtools.asPath)
   // ......
  }
}

So, it’s all good? Unfortunately, no:

[ant:schemaexport] java.lang.ClassNotFoundException: com.sun.xml.bind.v2.ContextFactory
[ant:schemaexport]      at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471)
[ant:schemaexport]      at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:588)
[ant:schemaexport]      at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
[ant:schemaexport]      at org.hibernate.boot.cfgxml.internal.JaxbCfgProcessor.unmarshal(JaxbCfgProcessor.java:122)

After some debugging, I learned that javax.xml.bind.ContextFinder tries to load the class com.sun.xml.bind.v2.ContextFactory (which is on the ANT classpath!) using the “wrong” classloader. Instead of doing getClass().getClassLoader().loadClass("com.sun.xml.bind.v2.ContextFactory") is does something like Thread.currentThread().getContextClassLoader().loadClass("com.sun.xml.bind.v2.ContextFactory"), which throws a ClassNotFoundException.

The difference is that getClass().getClassLoader() returns the AntClassLoader that actually finds the JAXB runtime library. But Thread.currentThread().getContextClassLoader() returns Gradles own org.gradle.internal.classloader.VisitableURLClassLoader.

The class javax.xml.bind.ContextFinder is part of the official JAXB-API library, so there is no way for me to change its implementation. So, what can I do? Is there any way to make the VisitableURLClassLoader find the JAXB runtime library?


(uklance) #2

I’m guessing that you could hack the context classloader whilst ant is executing

Classloader ogClassloader = Thread.currentThread().getContextClassloader()
try {
   Thread.currentThread().setContextClassloader(getClass().getClassloader())
   doAntStuff(project) 
} finally {
   Thread.currentThread().setContextClassloader(ogClassloader)
} 

(Christian Ciach) #3

I guess this would involve writing my own ANT task that wraps Hibernate’s SchemaExportTask. Not elegant, but probably doable.


(uklance) #4

I wouldn’t have thought that was necessary, guessing you can do that in Gradle


(Christian Ciach) #5

Maybe I can. I will try it tomorrow. Thanks for the advice!


(Christian Ciach) #6

Unfortunately, this doesn’t work. Doing getClass().getClassloader() inside the build script returns a org.gradle.groovy.scripts.internal.DefaultScriptCompilationHandler$ScriptClassLoader. Doing ant.getClass().getClassLoader() returns the org.gradle.internal.classloader.VisitableURLClassLoader that is used for the ant task anyway.

I don’t see a way to access the AntClassLoader from inside the build script. Maybe writing my own AntTaskWrapperTask is the only solution. But all theses workarounds are really just ugly hacks. I guess with the spread of Java 11 many more people will encounter this issue.


(Christian Ciach) #7

Turns out, there is not just the SchemaExportTask (an ANT task), but also “SchemaExport”, a command line tool. I just had to convert my task to type JavaExec to call the command line tool. That’s even better than before.