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.

Actions

Information

16 responses

26 01 2010
Packaging all files into a single executable .jar - Java Forums

[…] more info, see my blog post on the […]

7 02 2010
Robert

“right-clicking the build.xml and clicking “Run Target > Other Targets > clean-and-fat-jar.”

Where exactly are you right clicking? I don’t see where targets are specified in NetBeans itself after modifying the build.xml for the project.

7 02 2010
Joshua Born

I neglected to mention that the right-clicking of the build.xml needs to occur in the Files window. I’ll edit the post to specify this.

22 04 2010
Moa

Excellent post and useful tip. Unfortunately “Libraries” marked in NetBeans aren’t copied into the FatJar (although code added as JARs are). Apologies, I don’t have a solution to contribute at this stage.

Thanks for posting your ant source. Worked a treat for me.

13 05 2010
bax

hi, thanks a lot for the post. Very interesting.
But I have a question.
I’ve an Hibernet application, and if I launch the application to the jar that I had create with your xml schema, I have an exception, because the application don’t find the persistence.xml, that it’s in the META-INF folder.
This is my exception:
Exception in thread “main” javax.persistence.PersistenceException: No Persistence provider for EntityManager named HibernateH2PU

Do you have any idea to resolve this problem?

Thanks and regards,
Bax

14 05 2010
Joshua Born

It sounds like you want to change the patternset declaration at line 06 of the code snippet. I think maybe adding in a <include name="META-INF/persistence.xml"/> might do what you are looking for.

16 05 2010
bax

Thanks ;)
I resolved, removing all the 06 line of the code snippet. Now all works perfectly, or almost so seems ;)

you’re really gentle.

Bax

16 06 2010
Carlos E. Justino

Boa noite, Parabens pelo seu post!
Foi de grande ajuda! Obrigado mesmo!
Bom resto de semana!

17 06 2010
Joshua Born

Obrigado pelas suas amáveis palavras! Desejo-lhe as melhores, também. (Desculpem o meu Português. Isso foi traduzido do Inglês pelo Google).

20 08 2010
Grant Farley

This did not work for me. Build fails every time….says that the lib folder cannot be found. Any suggestions?

20 08 2010
Grant Farley

Nevermind, I found code that was working at : http://java.sun.com/developer/technicalArticles/java_warehouse/single_jar/
I hope others find this useful. I’ve been trying out to do this for a while.

21 08 2010
Joshua Born

I’m glad you found a solution that worked for you. I remember this being more of a pain than necessary, too. I’m not sure why they don’t ship NetBeans with such an option.

22 08 2010
Guy Freeman

I’m a newbie in NetBeans. Thank you for this useful tips!

As my gratitude, if you have any problem with Delphi, just Google or pm me. Thanks again :)

1 09 2010
minddumped

OMG, you’re my hero.
I will give it a whirl.

17 10 2010
chj

Many, many thanks, however: everything is fine until the Jar executable is doubleclicked. Then the libs (jar files) are not available to the running program even if their class files are inside the Jar and everything looks fine. It means that I can rightclick build file from inside netbeans and choose run, and the app will run and use the additional libs (added as jar libs to the project). But I cannot start from outside Netbeans. Any intelligent guess ?

Secondly, is there a way to speficy another place for the original lib files than the /lib ? Because I have a general library fold inside my netbeans folder and the best solution would be to go there and fetch the lib-as-jar to be unjared and then rejared, rather than copying these lib-as-jar files into the /jar. Not very important, but I mention it in case there is a simple solution. Thanks again.

23 10 2010
SplashMyBandit

Awesome. A very handy modification for NetBeans tasks. Thanks for the tip.




Follow

Get every new post delivered to your Inbox.