Sunday, October 31, 2010

Classpath setting in an executable jar file


Recently while working for a customer project, I found out that its quite handy to package the product (application) jar as a single executable jar file. The jar file has a MANIFEST file and all the related application class files. In the manifest file I could add the location of all third party libraries and the main class as follows :

Manifest File :

Main-Class : com.x.y.z.MainServer
Class-Path : . ../config ../lib/activation.jar ../lib/activemq-core.jar ../lib/ojdbc.jar ......

This was perfect and I could start the application by firing the following command :

Command : "java -jar application.jar"

The application has classpath dependency on the config folder (property and other configuration files for the application) and the third party jars it used. This all was now specified in the Manifest and I had the cleanest deployment.

Now, this being a databse independent product, there was a requirement not to add the driver jars (default was the ojdbc.jar) in the Manifest and hence not ship it with the product. The customers wanted to specifiy there own tested drivers for their specific databases.

Solution 1 :

The obvious solution which comes into mind is removing driver jar (ojdbc.jar in this case) from the manifest and specifying it in the class path like :

Manifest File :

Main-Class : com.x.y.z.MainServer
Class-Path : . ../config ../lib/activation.jar ../lib/activemq-core.jar ......

Command : "java -cp ../lib/ojdbc.jar -jar application.jar"
or
Command : "java -classpath ../lib/ojdbc.jar -jar application.jar"

The ojdbc.jar could be easily replaced by a custom customer specific driver.

But this is not possible and you would end up with a error such as Driver's ClassNotFoundException . Because when you execute a jar file you can not override or append the classpath present in the manifest. In a nutshell '-cp' and '-classpath' options are ignored by the vm with '-jar' option.

Solution 2 :

You remember the basics of classloaders then you still have access to the bootstrap classloader and extension classloader. These are searched when the classes are missing, through your application classloader (the others being its parent).

1. To add this additional jar to extension classpath, you have to place it in the /ext folder under your java installation. This is not an ideal behaviour for a production setup.

2. To add a jar file to the bootstrap classloader, you have to execute the application with the following command :

Command : "java --Xbootclasspath/a:../lib/ojdbc.jar -jar application.jar"

This will take into account the application classpath from the manifest inside the application.jar and also add the ojdbc.jar into the bootstrap classloader's classpath.

But again, this could potentially conflict with some third party libraries you use which could potentially in turn instrument your application classes at runtime. Personally, I would not recommend this as a healthy approach.

Solution 3 :

The Manifest file would not have a Main-Class defined but just the Class-Path as follows :

Manifest File :

Class-Path : . ../config ../lib/activation.jar ../lib/activemq-core.jar ......

The command used to run application would be :

Command : "java -cp ../lib/application.jar:../ojdbc.jar com.x.y.z.MainServer"

This works and this is what I used. Not the cleanest of all but possibly the best solution available.

The third party jars defined in the application.jar file's Manifest, are still picked up by the VM. The application.jar is in the class path and resolution of dependencies of application classes is also searched through the classpath defined in its manifest.

In a nutshell, the classpath defined in Manifests of the jar files in the application classpath is appended to the application classpath.