The Elusive Uber Jar

Diwaker Gupta
Floating Sun
Published in
2 min readSep 29, 2015

Have you ever built or shipped a (large) Java application? If so, you probably packaged everything together in a single jar/war file to make it easy to download, deploy and run. Despite being such a seemingly common (and simple) task, building this “jar with dependencies” — commonly called an “uber jar” or a “jar of jars” — remains a fragile and needlessly complicated task.

Why build an Uber Jar?

If you are shipping a Java application, nothing beats the simplicity of
java -jar your-app.jar for testing and even production deployment. My favorite example is Gerrit, the excellent code-review tool. Some very smart folks have also suggested this is a good idea.

Building an Uber Jar

The Internet is full of strange things and several ways of building an uber
jar. Two approaches seem most common.

Explode and (re)assemble

Solutions in this category include the ever popular Maven Shade plugin and it’s sibling the Maven Assembly plugin. For this discussion I’ll focus on shade.

The way shade works is that it expands every single jar file that your app depends on, combines all the files (including your app classes) in a single directory and repacks them into a single jar file. While this approach (mostly) works, it has several drawbacks:

  • Performance: unpacking bunch of jars and repacking them is wasteful. Until recently shade didn’t even buffer output (see MSHADE-134), making it particular egregious on encrypted or networked filesystems.
  • Overlaps: Aggregating classes & resources is OK, until they conflict. In an application with (many) transitive dependencies, this is fairly common, thus necessitating hacks like resource transformers. Unfortunately, such transformations break legitimate use-cases for multiple files of the same name/path in the CLASSPATH. E.g., theServiceLoader API in the JDK or Typesafe’s config library.
  • Security: When using shade to build an uber jar, the following fragment seems standard practice.

Unfortunately this works around Java’s digital signature checks to verify signed JARs.

<excludes> 
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>

Custom Classloaders

The other popular approach is to use a custom classloader that can correctly load classes and other resources from nested jar files. Solutions in this category include One-JAR and JCL among others.

Unfortunately none of these classloaders are perfect and suffer from one limitation or another. They are often unmaintained, lack compatibility with newer JDKs, don’t work well with dependency-injection frameworks like Guice and Dagger and don’t integrate well with build systems like Maven and Gradle.

Holy Grail

Ideally, the JVM itself should just support this.

# I can do this
$ java -cp dep1.jar:dep2.jar:dep3.jar -jar myapp.jar
# And this
$ java -cp libs/* -jar myapp.jar
# Yet I can’t do this:
$ jar cvf uber.jar libs/* myapp.jar
$ java -jar uber.jar # fail. WHY??

--

--

Diwaker Gupta
Floating Sun

Geek, open source enthusiast, software architect, virtualization research