Stuff Yaron Finds Interesting

Technology, Politics, Food, Finance, etc.

Creating Executable Jar Files That Contain Jar Files

Naive Java user that I am when I built a Java program that contains Jar files for Xerces and MySQL and such I assumed that Eclipse would be able to create a Jar file for my program that contains these other Jar files. That part was right, Eclipse can do that, what Eclipse can't do is set up the class paths correctly so my custom generated Jar file can't find the classes it needs. Thankfully, One-Jar came to the rescue.

Building a Jar File That Contains Jar Files

My project has a bunch of classes, which is no big deal, but it also includes a bunch of Jar files for things like Xerces & MySQL. Unfortunately the classpath argument in a Jar file's manifest works in some bizarre way too hard for my limited intellect to grasp with the result that, in short, you can't put Jar files into Jar files and expect things to work well.

When I asked the Java gurus at work how to handle this they said I was supposed to unzip the Jar files I was depending on, flatten out their directory structures and then jar up the whole bloody thing. This is as nasty as it sounds and the way most people deal with it is they develop using the Jar files directly (something Eclipse handles wonderfully) but when the time comes to create their own Jar file they have ANT scripts which do the dirty work.

I, however, took one look at ANT and realized that I could easily waste days trying to understand it. I had no doubt that all I needed was 10 lines of ANT XML but figuring out which ten lines would take me forever.

Thankfully however I found a program called One-Jar which is designed to create a Jar file that contains other Jar files and to make all the dependencies work. It does this through the nasty expedient of introducing its own class loader (a time honored Java hack) who does all the dirty work. I'm probably a bit thick but I found the instructions on the One-Jar home page a bit hard to follow. Here's my own even harder to read version.

Setting Up to use One-Jar

I have a directory called JavaStuff that contains within it a directory called MyJavaCode which contains all of my Java classes as well as the Jar files my classes make use of, e.g. Xerces and MySQL. To make it easy for me to create my custom Jar file I created a child directory to JavaStuff called onejarstruc which itself contains three subdirectories, main, lib and boot. So the file structure looks like:

                        JavaStuff 
/ \
MyJavaCode onejarstruc
/ / | \
Jar Files & Java Classes main lib boot

What matters in this set up are the directory names "main" and "lib". One-Jar depends on finding exactly those names to do its magic.

I then copied the Jar file I downloaded from One-Jar into the boot directory and executed "jar -xvf one-jar-boot-0.95.jar" in terminal which exploded the Jar file's contents into the boot directory.

The next step was to create a manifest file. This file's only job is to point to the class that contains the main method that should be called when my Jar is executed. To do this I created a file called blog.manifest inside of the MyJavaCode directory. It's contents are:

Main-Class: org.goland.blog.ReadPost

Making It Easy To Use One-Jar

The process of creating my custom Jar file using One-Jar has the following steps:

  1. Remove any old files hanging around

  2. Create the "core.jar" file which just contains my Java classes and is set to use blog.manifest and put the resulting jar file in the "onjarstruc/main" subdirectory.

  3. Move the Jar files I depend on (like Xerces, MySQL, etc.) to "onjarstruct/lib"

  4. Create the final jar file, I call it blogupdater.jar and put inside of it the contents of the main and lib directories

  5. Update the contents of blogupdater.jar to contain the manifest and classes provided by One-Jar

The script that does this for my project is:

#!/bin/sh
# Configuration
commonRoot=/Users/xxx/yyy/JavaStuff
java5BashRC=$commonRoot/java_functions_bashrc
oneJarRoot=$commonRoot/onejarstruc
# The compound Jar file with the One-Jar class loader
updateJarLoc=$commonRoot/blogupdater.jar
javaCodeRoot=$commonRoot/MyJavaCode
# The manifest file for core.jar
coreManifest=$commonRoot/MyJavaCode/blog.manifest

# Clean up
rm $updateJarLoc
rm $oneJarRoot/main/*
rm $oneJarRoot/lib/*

# Create core jar file (org is the directory with my java code)
jar cmf $coreManifest $oneJarRoot/main/core.jar -C $javaCodeRoot org

# Move helper jar files over to library
cp $javaCodeRoot/*.jar $oneJarRoot/lib

# Create the compound jar file
jar cf $updateJarLoc -C $oneJarRoot main -C $oneJarRoot lib

# Change the classloader for the compound jar file
jar -uvfm $updateJarLoc $oneJarRoot/boot/boot-manifest.mf -C $oneJarRoot/boot .

I save this script as "buildjar.sh" and after running "sh buildjar.sh" in terminal I have a nice shinny new blogupdate.jar file which I then can execute. Thanks to the magic of One-Jar all of the references in my Java code to classes inside of the subsidiary Jar files work just fine.

4 Responses to Creating Executable Jar Files That Contain Jar Files

  1. Rick says:

    Hi Yaron,
    I have various java programs that are invoked with command syntax like:
    java -jar program1.jar

    At the moment the dependency files are in the same directory as the programs. I would like to move all the dependency files/jars to one subdir then call the programs using syntax:
    java -jar program1.jar -classpath subdir

    I’m not a java programer so I’m not sure – does your post mean that this will/may cause the program not to find the classes it needs, even if I use the -classpath option?

    Do you know any other resources that discuss this topic?

    Thanks
    Rick

  2. Forrest says:

    I came up with the ten lines needed to extract all the library jars into your destination build dir:

    Seems simpler than this jars-within-jars hack to me.

  3. Forrest says:

    Oops, let’s try that again:
    (what, no preview?)


    <target name="expand.lib.jars">
    <unjar dest="${build.dir}">
    <fileset dir="${lib.dir}"
    includes="**/*.jar"/>
    <patternset>
    <include name="**/*.class"/>
    </patternset>
    </unjar>
    </target>

    Seems simpler than this jars-within-jars hack to me.

  4. MarkG says:

    Alternative is to use http://www.jdotsoft.com/JarClassLoader.php because it simplier, allows nested JARs, does not require strict JAR structure.

Leave a Reply to Rick Cancel reply

Your email address will not be published. Required fields are marked *