building an executable jar from other jars

I started the day cursing the stupid jar format, but ended the day smiling. I needed to build a stand-alone executable jar which accessed a MySQL database but kept getting all sorts of build-time and run-time errors.

Normally to make an executable jar I use the excellent pack ant task which build a minimal jar file by trawling all the class dependencies in yout code and including just the required classes from the classpath. For example:

<target name="jar">
   <taskdef name="pack" 
      classname="org.sadun.util.ant.Pack" classpath="lib/pack.jar" />
   <pack 
       classes = "${root.classes}"
       targetJar = "dist/${project.name}.jar"
       manifestMainClass = "${main.class}"
       excludePkg = "java,sun"
       includePkg = "com,org,javax"
   >
      <classpath refid="classpath"/>
   </pack>
</target>

Just set up the main.class property to the main class of the application, and set the root.classes property to a class which refers to everything you need (in many cases this will be the same main class) and you get a lovely small executable jar.

However, trying this approach with MySQL cause a bunch of problems. First I needed to add a load of (apparently dynamically loaded) MySQL class names such as com.mysql.jdbc.Driver.

I thought I was doing well when I had resolved that issue, but then I hit an even harder problem:

java.lang.RuntimeException: Can't load resource bundle due to underlying exception
java.util.MissingResourceException: Can't find bundle for base name com.mysql.jdbc.LocalizedErrorMessages, locale en_GB

Despite spending an hour or so searching and trying, I could not convince pack to find it. So I had to look elsewhere.

The solution I eventually chose was “one-jar” – a trick which subverts the normal jar execution process and creates a custom classloader to resolve jars within a jar. I still use pack to minimise the amount of other classes included in my project, but explicitly include the whole MySQL driver jar.

My ant target now looks like:

<target name="jar">
   <taskdef name="pack" 
      classname="org.sadun.util.ant.Pack"
      classpath="lib/pack.jar" />
   <pack 
       classes = "${root.classes}"
       targetJar = "tmp/main.jar"
       manifestMainClass = "${main.class}"
       excludePkg = "java,sun"
       includePkg = "com,org,javax"
   >
      <classpath refid="classpath"/>
   </pack>
   <taskdef name="one-jar" 
      classname="com.simontuffs.onejar.ant.OneJarTask"
      classpath="lib/one-jar-ant-task-0.96.jar" onerror="report"/>
   <one-jar destfile="dist/${project.name}.jar" manifest="src/main/files/${project.name}.mf">
      <main jar="tmp/main.jar"/>
      <lib>
         <fileset file="lib/mysql-connector-java-5.0.4-bin.jar"/>
      </lib>
   </one-jar> 
</target>

This builds all of my application except the MySQL bits using pack into a temporary “main.jar”, then uses one-jar to build another jar which refers to both main.jar and the MySQL driver jar.

I did need to create an explicit manifest file this way (usually, pack is smart enough to make it for me)

Main-Class: com.simontuffs.onejar.Boot
One-Jar-Main-Class: org.example.project.Main

I now have a working stand-alone executable jar which can access a MySQL database. Cool.

Read more at Deliver Your Java Application in One-JAR™ ! and Java: Using ONE-JAR