Developer's Daily Java Education
  front page | java | perl | unix | DevDirectory
   
Front Page
Java
Education
Pure Java
Articles
   

Create an executable jar file by dynamically referencing the jar files you depend on

A couple of days ago I had a situation come up where I wanted to distribute a Java application that I wrote as a jar file. The problem I ran into is that my jar file depended on quite a few other open source projects that were also distributed as jar files, and I wanted a good way to build my application and also distribute it to my users.

I've seen people provide their applications as batch files or shell scripts to get around this problem, but I knew there must be another way where I could just provide my application as an executable jar file. Beyond this, I also wanted to make sure that I could build my application reliably using ant. After a number of different attempts, I finally found a solution to the problem.

What my distribution looked like

The approach I ended up with was to deliver my application as one jar file, and then include all my dependencies (the other jar files) in the same directory with my jar file. So, if my application was delivered as my-application.jar, I provided it, my distribution directory ended up looking a little like this:

my-application.jar
commons-collections.jar
commons-dbcp-1.1.jar
commons-pool-1.1.jar
log4j-1.2.8.jar
mysql-connector-java-3.1.12-bin.jar

By creating a custom manifest file and including that manifest file in my jar file, users could then start my application by simply typing:

java -jar my-application.jar

at the command line. This is exactly what I wanted to achieve.

How I built the application

I've seen approaches where developers hard-code a classpath in their ant build script, and then include that classpath in a manifest file in their jar file. Including the classpath in the manifest file is the right approach to this problem, but hard-coding the classpath is not. As you can imagine, a hard-coded approach is fragile, and will break any time you forget to add a new jar file dependency to the list. (And unfortunately you won't know it's broken until you try to run your application, and hit the part of the application where the dependency is missing.)

A much better approach is to build the classpath dynamically. I built my application using ant, and built my classpath dynamically using a task named pathconvert, and a mapper named flattenmapper. The following segment of my build.xml file shows the commands that I used:

<!-- convert classpath to flat list for use in manifest task -->
<pathconvert property="mf.classpath" pathsep=" ">
    <path refid="build.class.path" />
    <flattenmapper />
</pathconvert>

This code says "Create a new property named mf.classpath from the property build.class.path, and when you build mf.classpath, take that build.class.path (which is essentially a list) and flatten it into a space-separated list of files that represents my build class path." This command effectively turns this list:

commons-collections.jar
commons-dbcp-1.1.jar
commons-pool-1.1.jar
log4j-1.2.8.jar
mysql-connector-java-3.1.12-bin.jar

into a string that looks like this:

commons-collections.jar commons-dbcp-1.1.jar commons-pool-1.1.jar log4j-1.2.8.jar mysql-connector-java-3.1.12-bin.jar

This string then becomes the classpath entry when you create your manifest file.

Building the manifest file

The second part of the solution is properly building the manifest file for your jar file. To create my manifest file I developed a portion of my build.xml file that looks like this:

<tstamp/><!-- needed for TODAY -->
<manifest file="MANIFEST.MF">
  <attribute name="Built-By" value="${manifest.built.by}"/>
  <attribute name="Created-By" value="${manifest.created.by}"/>
  <attribute name="Main-Class" value="${manifest.main.class}"/>
  <attribute name="Implementation-Version" value="${version.number}-b${build.number}"/>   
  <attribute name="Built-Date" value="${TODAY}"/>
  <attribute name="Class-Path" value="${mf.classpath}" />
</manifest>

If you read the specification on manifest files you'll see that a lot of those lines are boilerplate, but the last attribute that looks like this:

<attribute name="Class-Path" value="${mf.classpath}" />

is the one that tells my jar file to use the space-separated list of jar files as its classpath when it runs.

The beauty of this approach is (a) I build my classpath dynamically, based on the jar files I've included in my build path (which is essentially my lib directory), (b) I use that dynamically-created classpath in my manifest file, and (c) I also copy all the necessary jar files to my deployment directory, so the entire process of building a distribution of my application (including all dependencies) is automated.


The build.xml file

To wrap this up, here's the entire package target from my ant build script, along with an earlier part of the build script where I create my initial classpath. To keep this build script flexible I do use a large number of variables that I defined earlier in my ant build script, but hopefully those variables won't obscure what I'm trying to show in this article, which is how to dynamically create a classpath you can use to create a distributable, executable jar file.

With that being said, here are a few snippets of code from my ant build script.

<!-- First, I create my classpath from all the jar files in my lib directory -->
<path id="build.classpath">
  <fileset dir="lib">
    <include name="**/*.jar" />
  </fileset>
</path>

<!-- Next, my package task uses that classpath -->
<target name="package" depends="compile">
  <echo>=== PACKAGE ===</echo>
  
  <!-- convert classpath to flat list for use in manifest task -->
  <pathconvert property="mf.classpath" pathsep=" ">
      <path refid="build.classpath" />
      <flattenmapper />
  </pathconvert>
  
  <tstamp/><!-- needed for TODAY -->
  <manifest file="MANIFEST.MF">
    <attribute name="Built-By" value="${manifest.built.by}"/>
    <attribute name="Created-By" value="${manifest.created.by}"/>
    <attribute name="Main-Class" value="${manifest.main.class}"/>
    <attribute name="Implementation-Version" value="${version.number}-b${build.number}"/>   
    <attribute name="Built-Date" value="${TODAY}"/>
    <attribute name="Class-Path" value="${mf.classpath}" />
  </manifest>
  
  <jar basedir="${dest.dir.classes}" 
     destfile="${package.file}"
     includes="**/*.*"
     excludes="**/*Test*"
     manifest="MANIFEST.MF" />

  <copy todir="${dest.dir}">
    <fileset dir="${lib.dir}">
      <exclude name="${lib.cobertura.dir}" />
      <exclude name="junit*" />
      <include name="*.jar"/>
      <include name="*.zip"/>
    </fileset>
  </copy>
  <!-- move this file before the 'jar' task (and put it in the 'classes' dir) if you want to 
       include it in the jar -->
  <copy file="${resources.dir}/log4j.properties" tofile="${dest.dir}/log4j.properties" overwrite="true" />
  <copy file="${resources.dir}/${properties.file}" tofile="${dest.dir}/${properties.file}" overwrite="true" />
  <delete dir="${dest.dir.classes}" />
</target>

References

Here's a brief list of the references I used in developing this tutorial:


What's Related


Copyright 1998-2008 DevDaily Interactive, Inc.
All Rights Reserved.