Skip to content

Different ways to package Java applications: Fat, Skinny, Thin, Hollow

Notifications You must be signed in to change notification settings

jamesfalkner/java-packaging-demo

Repository files navigation

Packaging Java Applications

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.

Fat (Uber) JARs

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.

How to use this repository

Just clone it:

git clone https://github.com/jamesfalkner/java-packaging-demo

And then play along below by following the steps for each technology:

WildFly Swarm Fat JAR

  1. Build the app:

    cd wildfly-swarm-fat-thin
    mvn clean package
  2. Run Fat JAR with java -jar target/weight-1.0-swarm.jar

  3. Then access using http://localhost:8080/api/greeting

  4. You should see something like hello from WildFly Swarm Far JAR, the date is 2017-12-07

  5. Type CTRL-C to stop it.

Spring Boot Fat JAR

  1. Build the app:

    cd spring-boot-fat
    mvn clean package
  2. Run Fat JAR with java -jar target/greeting-spring-boot-fat.jar

  3. Then access using http://localhost:8080/api/greeting

  4. Type CTRL-C to stop it.

Dropwizard Fat JAR

  1. Build the app:

    cd dropwizard-fat
    mvn clean package
  2. Run Fat JAR with java -jar target/greeting-dropwizard-1.0.0.jar server ./greeting.yml

  3. Then access using http://localhost:8080/api/greeting

  4. Type CTRL-C to stop it.

Eclipse Vert.x Fat JAR

  1. Build the app:

    cd vertx-fat
    mvn clean package
  2. Run Fat JAR with java -jar target/greeting-vertx-fat-1.0.0-fat.jar

  3. Then access using http://localhost:8080/api/greeting

  4. Type CTRL-C to stop it.

Exciting, eh?

Thin WARs/JARs

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.

WildFly Swarm Thin WAR

  1. 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.

Thin JAR with Spring Boot

This variant uses the Spring Boot Thin Launcher which is a Maven plugin for building thin JARs. See its usage in the POM file

  1. Build the thin JAR:
    cd spring-boot-thin
    mvn clean package
  2. Run thin jar with java -jar target/thin/root/greeting-spring-boot-thin.jar. This Thin JAR finds its dependencies automatically in the thin/root/repository directory.
  3. Then access http://localhost:8080/api/greeting
  4. Type CTRL-C to stop it.

To build a docker image with the thin JAR in a separate layer:

  1. Build the image with docker build -t spring-thin:latest .
  2. 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) in target/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
  3. 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
    
  4. Re-build application with mvn clean package
  5. Re-build docker image with docker build -t spring-thin:latest .
  6. 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

Thin JAR with Eclipse Vert.x

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):

  1. Build the thin JAR:
    cd vertx-thin
    mvn clean package
  2. 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:

  1. Build the image with docker build -t vertx-thin:latest .
  2. 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 layer Step 6/6 : ADD target/greeting-vertx-1.0.0.jar $VERTICLE_HOME/ contains the Thin JAR of the application.
  3. 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
    
  4. Re-build application with mvn clean package
  5. Re-build docker image with docker build -t vertx-thin:latest .
  6. 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!

Skinny WAR with WildFly Swarm

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.

  1. To build the Fraction:
    cd joda-fraction
    mvn clean package install
    This will install the Fraction to your local Maven repository (~/.m2/repository)
  2. Next, build the Hollow JAR which will be able to run it:
    cd wildfly-swarm-skinny
    mvn clean package
    This will result in the Hollow JAR at target/weight-skinny-1.0-hollow-swarm.jar and the Skinny WAR at target/weight-skinny-1.0.war.
  3. 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
  4. Then access using http://localhost:8080/api/greeting
  5. Press CTRL-C to stop it

To build a docker image with the hollow JAR and skinny WAR in a separate layer:

  1. Build the image with docker build -t swarm-thin:latest .
  2. Notice that the container image is split in two separate layers, one for the app, and one for the hollow JAR.
  3. 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
    
  4. Re-build application with mvn clean package
  5. Re-build docker image with docker build -t swarm-thin:latest .
  6. Notice that just as in the Spring Boot example that the only re-built layer is the one containing the (2kb) skinny WAR.

Resources

About

Different ways to package Java applications: Fat, Skinny, Thin, Hollow

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published