Home United States USA — software The Skinny on Fat, Thin, Hollow, and Uber

The Skinny on Fat, Thin, Hollow, and Uber

175
0
SHARE

Which type of JAR should you use for packaging a Java project and when? This guide covers all of the major JAR types and their ideal use cases.
I was recently playing around with various techniques for packaging Java microservices and running on OpenShift using various runtimes and frameworks to illustrate their differences (WildFly Swarm vs. WildFly, Spring Boot vs. the world, etc.) . Around the same time as I was doing this, an internal email list thread ignited discussing some of the differences and using terms like Uber JARs, Thin WARs, Skinny WARs, and a few others. Some folks were highlighting the pros and cons of each, especially the benefits of the thin WAR approach when combined with docker image layers.
And I thought to myself: Isn’ t this what everyone is already doing? Do developers really have to think about this stuff? But before I get into that, I want to define the various terms I’ ve heard and at least get it straight in my own head.
The Qualifiers (in increasing order of logical size) :
Now let’s define how the qualifiers map to the world of Java applications and package types (JAR, WAR, etc) .
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.
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.
Same as a Thin WAR, except using the JAR packaging format. Typically, this is used by specialized applications/plugin architectures that use the JAR packaging format for its purpose-built plugin or runtime artifacts. For example, the.kjar format from Drools .
While less well known than its brethren, a skinny WAR is thinner than a Thin WAR because it does not include any of the 3rd-party libraries that the app depends on. It ONLY contains the (byte) code that you as the developer literally type in your editor. This makes a lot of sense in the docker world of layered container images, where layer size is important for DevOps sanity, and Adam Bien has done an awesome job demonstrating and explaining this. More on this later.
The same as a Skinny WAR, except using JAR packaging and frameworks built around it such as WildFly Swarm and Spring Boot. This also makes a ton of sense for CI/CD sanity (and your AWS bill) – just ask Hubspot. Here you take a Thin WAR and remove all the 3rd-party dependencies. You’ re left with the smallest atomic unit of app (attempting to go smaller sounds like a terrible idea but with Java 9/JPMS is might be possible) , and it must be deployed to a runtime that is expecting it AND has all of the other needed bits to run the app (such as a Hollow JAR)
This is a Java application runtime that contains “just enough” app server to run applications but does not contain any applications itself. It can run on its own but isn’ t that useful when running on its own since it doesn’ t contain applications and won’ t do anything other than initializing itself. Some projects like WildFly Swarm allow you to customize how much is “just enough” while others (like Paraya Micro, or TomEE) provide pre-built distributions of popular combinations of runtime components, such as those defined by Eclipse MicroProfile.
The rise of for-rent computing and the popularity of DevOps processes, Linux containers, and microservice architectures has made app footprint (the number of bytes that make up your app) important again. When you’ re deploying to dev, test, and production environments multiple times a day (sometimes hundreds per hour or even 2 billion times a week) , minimizing the size of your app can have a huge impact on overall DevOps efficiency, and your operational sanity. You don’ t have to minimize the lines of code in your app, but you should reduce the number of times your app and its app dependencies have to pass across a network, move onto or off of a disk, or be processed by a program. That means breaking your app into different packaged parts so that they can be properly separated and treated as such (and even versioned if you so choose) .
With cloud native microservices, this means using layered Linux container images. If you can separate your app’s components into different layers, putting the most frequently changing parts of your app on “top” and the least frequently changing parts on the “bottom”, then every time you re-construct a new version of your app you are in reality only touching the highest levels. Saving time across the board for storage, transport, and processing of the new version of your app.
It depends. The pros/cons of each have been discussed by others (here, here, and here) . Fat/Uber JARs are attractive due to their portability, ease of execution in IDEs and its all-you-need-is-a-JRE characteristic. But the more you can separate the layers (into skinny/thin) , the more you will save on network/disk/CPU further down the line. There’s also the added benefit of being able to patch all the layers independently, for example, to subscribe and quickly distribute fixes for security vulnerabilities to all of your running apps) . So, you have to decide amongst the tradeoffs.
WildFly Swarm is a mechanism for packaging Java applications that contain “just enough” functionality to run the app. It has an abstraction called a Fraction, each of which embodies some functionality that apps need. You can select which Fractions you need, and package only those fractions along with your app to produce a minimized and specialized runnable image for your app.
WildFly Swarm has the ability to create many of the above types of packaged apps. Let’s look at what it can do and the resulting size (s) of various packaging options, and how it applies in the world of layered Linux container images. We’ ll start with the Fat/Uber JAR and work our way down. Grab the code and follow along:
The sample app is a simple JAX-RS endpoint that returns a message. It has one direct dependency on Joda Time, which we’ ll use later on. For now, let’s make a Fat JAR (which is the default mode of WildFly Swarm) using the code in the fat-thin/ folder:
You can test it out with java -jar target/weight-1.0-swarm.jar and then curl http: //localhost: 8080/api/hello in a separate terminal window.
Not bad – 45M in size for a completely self-contained FAT jar. You could stuff this in a docker image layer every time you rebuild, but that 45M will grow as you actually implement a real-world app. Let’s see if we can do better.
As part of the above build of the Fat JAR, WildFly Swarm also created a Thin WAR so no rebuild is necessary. Take a look at the Thin WAR:
Now we’ re getting somewhere. This 512K Thin WAR could be deployed to any app server (such as WildFly itself) . So if you stuff this much smaller file into your upper docker layers while your rarely-changed app server lives in a lower layer (as Adam Bien demos multiple times) , you’ ll save quite a bit of time and energy during multiple builds. Let’s keep going.
You may have noticed in the demo app that it’s super-simple – only a few lines of code. So why does it take 512K to hold it? Most of the 512M in the Thin WAR is taken up by our direct dependencies – in this case, the Joda Time library:
If we continue to add direct dependencies (like database drivers and other things virtually every production app will need) , our Thin WAR will also grow and grow in size over time.

Continue reading...