Fat JARs out of NetBeans

26 01 2010

I’ve been using the NetBeans IDE lately, both for Ruby and for Java work. I’ve liked it thus far. It feels leaner and less bloated than Eclipse. Java in particular requires an IDE for efficient development work. NetBeans does a good job of managing your package hierarchy, handling libraries, and creating all those ugly Ant build scripts – all the things that make developing in Java a pain.

Recently a JAR deliverable was requested for a project I’ve been working on for about two weeks now. I set about writing my main method, hitting the “Clean and Build” option in NetBeans from time to time and playing around with the JAR it would put in my project’s dist directory.

Some time into it, I noticed something fishy going on. In particular, I added an XML file to my library path that was a configuration file which had to be on the classpath. Suddenly my executable JAR stopped working with a ClassNotFoundException. Looking at the contents of my JAR, I noticed that it referenced each individual library JAR in my project’s lib directory except in a lib directory that was supposed to be in the same directory as the executable JAR. This lib directory was popping up in the dist directory before, but suddenly was absent. It turns out if you add anything to your library configuration in NetBeans that isn’t a JAR file, then it doesn’t copy your library JARs over. Lame. Removing the XML file from the library configuration fixed this temporarily, but I wanted a solution to the bigger problem.

The whole point of an archive file is that it is just one file. Why, when asked to produce a JAR of my work, would I reply, “Sure. Here is the JAR. . . . and here are 30 other JARs that must be kept with the JAR according to this relative path.” Ideally, rather than distributing my JAR with a bunch of other JARs, I would like to zip everything into one big JAR. I heard there was a plugin for Eclipse called “Fat Jar” that did this, but googling this problem for NetBeans didn’t turn up anything.

I therefore rolled up my sleeves and began digging through the Ant build files that NetBeans generates for you. Unsurprisingly, they are quite convoluted. My approach was simple brute force. Go through all the JARs in my project’s lib directory and unjar each one – minus the META-INF directory and whatever readmes, license files, and errata there is in the root directory – into the build/classes directory before the final JAR is zipped up.

NetBeans lets you create custom Ant targets and hook them into a point in the build process, such as before the contents of the build directory are zipped up and turned into a JAR in the dist directory. The actual build scripts are in a build-impl.xml file in the nbproject directory, and the build.xml at the project root is set aside for these hooks, complete with some documentation on what hooks are available. I began by trying to set a “-pre-jar” target to do my brute force work, but ran into two problems: one, the classpath of the resulting JAR was still set to look for this nefarious lib directory to be distributed with the JAR, and two, it takes a long time to do all that unzipping and zipping up again, and I didn’t want to do this every time I created a JAR, just when I was going to make a real distributable.

I therefore required my own Ant task. The result is below.

    <target name="-unjar-and-copy-lib-jars">
        <unjar dest="${build.classes.dir}">
            <fileset dir="lib">
                <include name="**/*.jar"/>
            </fileset>
            <patternset>
                <exclude name="META-INF/**"/>
                <exclude name="/*"/>
            </patternset>
        </unjar>
    </target>

    <target depends="init,compile,-pre-pre-jar,-pre-jar,-unjar-and-copy-lib-jars" name="fat-jar">
        <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
        <jar destfile="${dist.jar}">
            <fileset dir="${build.classes.dir}"/>
            <manifest>
                <attribute name="Main-Class" value="${main.class}"/>
            </manifest>
        </jar>
        <echo>To run this application from the command line without Ant, try:</echo>
        <property location="${dist.jar}" name="dist.jar.resolved"/>
        <echo>java -jar "${dist.jar.resolved}"</echo>
    </target>

    <target depends="clean,fat-jar" name="clean-and-fat-jar"/>

As of NetBeans 6.8, you should be able to drop this into the build.xml file of any Java project created in NetBeans and start producing “fat” JARs that are completely standalone. To do this, follow the steps below.

  1. Open the Files window in NetBeans.
  2. Locate the build.xml file, which should be in the root of your project.
  3. Double-click the build.xml file to open it in the editor.
  4. Copy and paste the above Ant targets into the build.xml file and save it.
  5. Right-click the build.xml in the Files window.
  6. In the menu that appears, select Run Target > Other Targets > clean-and-fat-jar.