You've heard of Fat/Uber JARs and are probably building them today. But every time you build one, a tree is chopped down in the forest to power the compute, disk and networking resources needed to deploy it to your production systems. Using this code you can explore the benefits and costs of Fat JAR packaging and exercise various options for slimming your apps and saving those trees using popular frameworks like Wildfly Swarm, Dropwizard, Spring Boot and Eclipse Vert.x.
Maven and in particular Spring Boot popularized this well-known approach to packaging, which
includes everything needed to run the whole app on a standard Java Runtime environment
(i.e. so you can run the app with java -jar myapp.jar
). The amount of extra runtime stuff
included in the Uberjar (and its file size) depends on the framework and runtime features
your app uses.
Just clone it:
git clone https://github.com/jamesfalkner/java-packaging-demo
And then play along below by following the steps for each technology:
-
Build the app:
cd wildfly-swarm-fat-thin mvn clean package
-
Run Fat JAR with
java -jar target/weight-1.0-swarm.jar
-
Then access using
http://localhost:8080/api/greeting
-
You should see something like
hello from WildFly Swarm Far JAR, the date is 2017-12-07
-
Type CTRL-C to stop it.
-
Build the app:
cd spring-boot-fat mvn clean package
-
Run Fat JAR with
java -jar target/greeting-spring-boot-fat.jar
-
Then access using
http://localhost:8080/api/greeting
-
Type CTRL-C to stop it.
-
Build the app:
cd dropwizard-fat mvn clean package
-
Run Fat JAR with
java -jar target/greeting-dropwizard-1.0.0.jar server ./greeting.yml
-
Then access using
http://localhost:8080/api/greeting
-
Type CTRL-C to stop it.
-
Build the app:
cd vertx-fat mvn clean package
-
Run Fat JAR with
java -jar target/greeting-vertx-fat-1.0.0-fat.jar
-
Then access using
http://localhost:8080/api/greeting
-
Type CTRL-C to stop it.
Exciting, eh?
If you’re a Java EE developer, chances are you’re already doing this. It is what you have been doing for over a decade, so congratulations you’re still cool! A thin WAR is a Java EE web application that only contains the web content and business logic you wrote, along with 3rd-party dependencies. It does not contain anything provided by the Java EE runtime, hence it’s “thin” but it cannot run “on its own” – it must be deployed to a Java EE app server or Servlet container that contains the “last mile” of bits needed to run the app on the JVM.
- Build the thin WAR:
cd wildfly-swarm-fat-thin mvn clean package
The Thin WAR is located at target/weight-1.0.war
and can be deployed to Java EE app servers like WildFly
For example, to download and run WildFly 11 with the Thin WAR deployed to it:
TMPDIR=`mktemp -d`
curl -skl http://download.jboss.org/wildfly/11.0.0.Final/wildfly-11.0.0.Final.tar.gz | (cd $TMPDIR; tar -xvzf -)
cp target/weight-1.0.war $TMPDIR/wildfly-11.0.0.Final/standalone/deployments
$TMPDIR/wildfly-11.0.0.Final/bin/standalone.sh
Then access with http://localhost:8080/api/greeting
Type CTRL-C to stop it.
This variant uses the Spring Boot Thin Launcher which is a Maven plugin for building thin JARs. See its usage in the POM file
- Build the thin JAR:
cd spring-boot-thin mvn clean package
- Run thin jar with
java -jar target/thin/root/greeting-spring-boot-thin.jar
. This Thin JAR finds its dependencies automatically in thethin/root/repository
directory. - Then access
http://localhost:8080/api/greeting
- Type CTRL-C to stop it.
To build a docker image with the thin JAR in a separate layer:
- Build the image with
docker build -t spring-thin:latest .
- Notice that the container image is split in two separate layers: The
thin/root/repository
directory containing app dependencies, and the app itself (Thin JAR) intarget/thin/root/greeting-spring-boot-thin.jar
:.... Step 5/6 : ADD target/thin/root/repository repository ---> 2834ca524f63 Step 6/6 : ADD target/thin/root/greeting-spring-boot-thin.jar app.jar ---> 041f07b12aca Successfully built 041f07b12aca Successfully tagged spring-thin:latest
- Make a change to the app source code to simulate a simple change, for example
echo 'class foo {}' >> src/main/java/com/example/demo/BoosterApplication.java
- Re-build application with
mvn clean package
- Re-build docker image with
docker build -t spring-thin:latest .
- Notice that the previously created dependency layer previously created from
thin/root/repository
is cached and re-used, saving time and energy on redeploys to your production clusters:Step 6/7 : ADD target/thin/root/repository repository ---> Using cache <--- *** Cached! ---> 2834ca524f63 Step 7/7 : ADD target/thin/root/greeting-spring-boot-thin.jar app.jar ---> 8b4e5ac9cd69 <--- *** Not cached Successfully built 8b4e5ac9cd69 Successfully tagged spring-thin:latest
This variant excludes the Vert.x dependencies from the app using <scope>provided</scope>
. There's probably a way to
do it better with the Maven Shade plugin.
The app is then combined with a pre-built docker base image containing the Vert.x runtime (thereby achieving its goal of separating app from runtime, in two separate container layers):
- Build the thin JAR:
cd vertx-thin mvn clean package
- Attempt to run thin jar with
java -jar target/greeting-vertx-1.0.0.jar
. This will fail due to the dependencies (Vert.x runtime) unable to be located.
To build a docker image with the thin JAR in a separate layer:
- Build the image with
docker build -t vertx-thin:latest .
- Notice that the container image is split in two separate layers. The first layer
Step 1/6 : FROM vertx/vertx3-exec
contains the Vert.x runtime and is downloaded from Docker Hub. The last layerStep 6/6 : ADD target/greeting-vertx-1.0.0.jar $VERTICLE_HOME/
contains the Thin JAR of the application. - Make a change to the app source code to simulate a simple change, for example
echo 'class foo {}' >> src/main/java/com/example/demo/BoosterApplication.java
- Re-build application with
mvn clean package
- Re-build docker image with
docker build -t vertx-thin:latest .
- Notice that the only un-cached layer is the thin JAR, saving time and energy on redeploys to your production clusters!
Now let's really get something going by going skinny!
You may have noticed that the Thin WAR created by WildFly Swarm is still ~500k, not because the project has 500k of code in it, but because it depends on a simple library (the Joda Time library ). You can see it here:
% unzip -l wildfly-swarm-fat-thin/target/weight-1.0.war|grep joda
634048 00-00-80 04:08 WEB-INF/lib/joda-time-2.9.9.jar
In earlier examples with Spring Boot, this library was placed alongside all the other libraries in a flat classpath, including Spring Boot itself, which provides convenience but requires special code if you want to isolate the app's classes from its runtime. WildFly Swarm has its WildFly heritage which means it uses classloader isolation to achieve protection but requires special handling to properly link external libraries to applications.
To remove the library and get down to the smallest possible app, you'll need to build a WildFly Swarm Fraction which will contain the necessary bits (which allow it to properly hook into the app at runtime, using JBoss Modules ). The fraction is then used when building a Hollow JAR, which is an executable (but initially useless) WildFly Swarm runtime to which the skinny app can be deployed.
- To build the Fraction:
This will install the Fraction to your local Maven repository (~/.m2/repository)
cd joda-fraction mvn clean package install
- Next, build the Hollow JAR which will be able to run it:
This will result in the Hollow JAR at
cd wildfly-swarm-skinny mvn clean package
target/weight-skinny-1.0-hollow-swarm.jar
and the Skinny WAR attarget/weight-skinny-1.0.war
. - Finally, run the Hollow JAR, passing it the path to the Skinny WAR to deploy:
java -jar target/weight-skinny-1.0-hollow-swarm.jar target/weight-skinny-1.0.war
- Then access using
http://localhost:8080/api/greeting
- Press CTRL-C to stop it
To build a docker image with the hollow JAR and skinny WAR in a separate layer:
- Build the image with
docker build -t swarm-thin:latest .
- Notice that the container image is split in two separate layers, one for the app, and one for the hollow JAR.
- Make a change to the app source code to simulate a simple change, for example
echo 'class foo {}' >> src/main/java/com/test/rest/HelloEndpoint.java
- Re-build application with
mvn clean package
- Re-build docker image with
docker build -t swarm-thin:latest .
- Notice that just as in the Spring Boot example that the only re-built layer is the one containing the (2kb) skinny WAR.