Recap: The Elusive Uber Jar
In a previous post I discussed various challenges and
pitfalls in creating an "uber" jar for deploying Java applications. Having
battled Maven Shade,
Maven Assembly and even Proguard out of desperation, I had
almost given up hope when I chanced upon Capsule.
What is Capsule? Let's ask the README:
Capsule is a dead-easy deployment package for standalone JVM applications. Capsule lets you package your entire application into a single JAR file and run it like this java -jar app.jar. That's it.
Oooh, I'm intrigued. But wait, there's more:
It can automatically download Maven dependencies when the program is first launched if you choose not to embed them in the capsule, and it can even automatically download a new version of your application when it is published to a Maven repository.
Capsule is brought to you by the fine folks at Parallel Universe
But why? I'm happy with Shade!
Are you really? Here are just some of the ways in which Capsule is better:
- Fast: since there's no jar explosion/reassembly required, Capsule packages much faster than Shade or Assembly
- Consistent experience across your build system of choice: Maven, Gradle. I'm sure ant and friends are not far off, if not already supported.
- Plays nice with the ServiceLoader API. No need for the ugly resource transformers you need with Shade.
- Plays nice with signed jars. No need to ugly (and unsafe!) excludes with Shade.
- Tons of nice new features unavailable in shade: JVM selection, download dependencies on the fly, automatic application updates, mode-specific parameters, ability to bundle in start-up scripts, plays nice with java agents and makes it easy to build really executable jars.
Need I go on?
How does it work?
Since the JVM doesn't natively understand a jar of jars, Capsule does the next best thing. It packages up all the dependencies within a jar along with a thin wrapper class (the
Capsule). When this jar is first launched, Capsule will extract all the dependencies into a temp directory, then fork a new process,
set the classpath to include the extracted jars (including your application's jar) and then finally invoke your application's entry point.
I won't discuss the advanced features (e.g. downloading Maven dependencies at runtime or system scripts) here -- check out the
README for more details.
Show me how!
The Capsule team has helpfully put together a demo project
to show usage for both Gradle and Maven based projects. Somewhat counter-intuitively,
to use Capsule with Maven actually relies on the
Assembly Plugin. Here's a sample assembly:
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd"> <id>capsule-full</id> <formats> <format>jar</format> </formats> <baseDirectory>/</baseDirectory> <dependencySets> <dependencySet> <outputDirectory>/</outputDirectory> <includes> <include>co.paralleluniverse:capsule:jar</include> </includes> <unpack>true</unpack> <unpackOptions> <includes> <include>Capsule.class</include> </includes> </unpackOptions> <scope>runtime</scope> </dependencySet> <dependencySet> <outputDirectory>/</outputDirectory> <excludes> <exclude>co.paralleluniverse:capsule:*</exclude> </excludes> <unpack>false</unpack> <scope>runtime</scope> </dependencySet> </dependencySets> </assembly>
And here's the
<dependency> <groupId>co.paralleluniverse</groupId> <artifactId>capsule</artifactId> <version>0.7.0</version> <scope>compile</scope> <optional>true</optional> </dependency> ... <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>2.4</version> <configuration> <appendAssemblyId>false</appendAssemblyId> <archive> <manifest> <mainClass>Capsule</mainClass> <classpathPrefix>lib</classpathPrefix> <addClasspath>true</addClasspath> <addDefaultImplementationEntries>true</addDefaultImplementationEntries> <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries> </manifest> <manifestEntries> <Application-Class>your.main.Class</Application-Class> <JVM-Args></JVM-Args> <System-Properties></System-Properties> </manifestEntries> </archive> <descriptors> <descriptor>src/main/resources/assemblies/capsule.xml</descriptor> </descriptors> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin>
I'm pretty happy with Capsule thus far. I've already used it in a few projects
and look forward to never using Shade again! Note that there is an unofficial
Maven Plugin if you'd rather not use the Assembly plugin.
So if you've been using Shade or OneJar or something else to package your app, definitely check out Capsule and spread the word!