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