One of my teammates ran head on into some dependency conflicts recently.¬† The application in question must use 2 sets of APIs from 2 vendors.¬† One for our content management system provider and the other from our search platform provider.
Unfortunately, the 2 APIs have conflicting dependencies and to make matters worse, the Content Management vendor hides these dependencies by wrapping all of their dependencies into a single jar file. (thirdparty-combined.jar)¬†¬† Having all the dependencies wrapped inside a single jar prevents me from being able to simply swap out the conflicting version.
The problem comes up when we put both commons-httpclient-3.0.jar and this thirdparty-combined.jar (which contains commons-httpclient-2.0) into the WEB-INF/lib directory of our application.¬† Java does not give us a method for controlling the priority of jars in WEB-INF/lib, so we are at the mercy of the app server which for some reason behaves differently on our local desktop, dev, and test tiers.
The simple solution would be to resolve the dependency by including only commons-httpclient-3.0.jar.¬† Unfortunately, thanks to the vendor’s decision to jam all of their dependencies into a single jar, we don’t have the option of removing the conflicting version.
To work around the problem, we had to ensure that commons-httpclient-3.0.jar would be loaded by a higher-level classloader (or parent classloader) to the web app’s classloader.¬† We accomplished this by packaging the app as an EAR file and adding commons-httpclient-3.0.jar to the ear file.¬† We then added a reference to the ear file’s copy of commons-httpclient-3.0.jar to the webapp’s manifest classpath in the war files MANIFEST.MF file.
On our app servers, the war manifest classpath is accessed by the classloader that is parent to the loader for WEB-INF/lib.¬† This ensures that the desired version of the library is loaded first.
I still need to do some research to see if I can find a cleaner solution, but this will allow us to meet our release deadline.
Things I don’t like about the way we resolved this include:
- Having to muck with the Manifest Classpath for the webapp
- Different app servers behave differently, so this may not work on another app server, or even on the next version of the app server
Ideally, I would like to find a way to have this loaded by the ear file’s classloader which can generally be expected to be the parent classloader for the included war files.¬† I just haven’t had time to mess about with it further.