Why is uberjar such a rare option?

(written by lawrence krubner, however indented passages are often quotes). You can contact lawrence at: lawrence@krubner.com, or follow me on Twitter.

I find this very surprising. My first serious exposure to the JVM was via Clojure, which has the awesome Leinengen build tool, which has an uberjar option. Therefore I thought uberjar was common on the JVM. But no. Now that I am working with Java, I find that it is rare for anyone to put jars inside of jars:

You can add jars to the jar’s classpath, but they must be co-located, not contained in the main jar.

That was in 2008, but there is this comment from 2013:

So here we are, 5 years later. It looks like this is still true. Very sad :( – T3rm1

Incredible. Why doesn’t Sun/Oracle focus on easier methods for us to distribute our code? Is this a case where a big corporation is blind to the needs of smaller companies, or this a case where Sun/Oracle wants enterprises to buy their expensive deployment/server frameworks, and so they have no incentive to solve this problem?

This apparently is still true:

If you’re trying to create a single jar that contains your application and it’s required libraries, there are two ways (that I know of) to do that. The first is One-Jar, which uses a special classloader to allow the nesting of jars. The second is UberJar, (or Shade), which explodes the included libraries and puts all the classes in the top-level jar.

I should also mention that UberJar and Shade are plugins for Maven1 and Maven2 respectively. As mentioned below, you can also use the assembly plugin (which in reality is much more powerful, but much harder to properly configure).

Or check out this bit from CodeHaus:

Manual Uberjarring

classworlds allows the creation of a single standalone jar for your project which may internally include any other additional jars that are required for your application. This allows for easy java -jar myapp.jar type of execution.

To create a standalone jar (aka, an uberjar), simply build your application’s jar as normal. Gather up all dependent jars and create a classworlds.conf for your application. Similar to other jar formats, a meta-directory is created within the uberjar, named WORLDS-INF/. It contains two directories:

WORLDS-INF/lib/

to contain all jars required by your application.

WORLDS-INF/conf/

to hold your classworld.conf file.

The classworlds.conf should be created as normal, with the special exception that the property ${classworlds.lib} points to the internal library directory WORLDS-INF/lib/ so that jars can be loaded from within the uberjar:

[app]
${classworlds.lib}/myApp.jar
${classworlds.lib}/someDependency.jar

The core classworlds jar needs to be placed at the root of the WORLDS-INF directory, named exactly classworlds.jar

Create the required directory structure, and populate it with the appropriate files. For example:

./assembly-dir/
WORLDS-INF/
classworlds.jar
lib/
myApp.jar
someDependency.jar
anotherDependency.jar
conf/
classworlds.conf

All that remains is unjaring the classes from classworlds-boot.jar into your assembly directory and creating your final jar. The final layout should appear like:

./assembly-dir/
WORLDS-INF/
classworlds.jar
lib/
myApp.jar
someDependency.jar
anotherDependency.jar
conf/
classworlds.conf
com/
werken/
classworlds/
boot/
Bootstrapper.class
InitialClassLoader.class
protocol/
jar/
Handler.class
JarUrlConnection.class

Now, simply create and distribute your standalone uberjar:

cd assembly-dir/
jar cvf myapp-standalone.jar .
java -jar myapp-standalone.jar

Why wasn’t this automated 10 years ago? Why is it automated for Clojure but not for the rest of the JVM?

Post external references

  1. 1
    http://leiningen.org/
  2. 2
    http://stackoverflow.com/questions/183292/classpath-including-jar-within-a-jar
  3. 3
    http://classworlds.codehaus.org/uberjar.html
Source