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