Building, Packaging, and Distributing Java EE Applications in 2017 (Part 2)
As Enterprise Fatjars become more popular by bundling application server components into JARs, let's examine them to see if they live up to memory-savings hype.
Join the DZone community and get the full member experience.Join For Free
In Part 1, an introduction to code and resource reuse was made. It was briefly explained how programs were built throughout history, with an emphasis on the Java ecosystem. The goal of the introduction was to clarify the reasoning behind historical J2EE design and to enlighten the design shift in modern Java EE.
In this final part, the modern way of building enterprise Java applications is examined. Not only pure are Java EE applications taken into account, but other complementary products like Spring living in the Java EE world are considered.
Fatjars on the Scene
Fatjars did not appear out of nowhere in 2015. In fact, fatjars have been with us for a long time. When creating “normal” Java SE applications, this means applications that aren't dependent on Java EE standard libraries — there are often some libraries used. For the program to run, they must be either present on the host machine, or can be distributed in (or beside) the Java archive (*.jar). This is a common practice, and all the major build tools even have plugins for that.
There is, however, one type of fatjar that is kind of new, or at least its popularity is growing in 2017. Personally, I call them Enterprise Fatjars, or EFJs. The idea behind them is to bundle application server components into the JAR itself. As a result, the application server “becomes a part” of a simple Java archive. Running such an application is then very simple, just use
java -jar applicationName.jar. This way, distribution has certain attributes perceived as advantages by some:
- Easy to start and stop, knowledge of an application server is not required.
- Exactly the dependencies required are bundled and nothing more.
I do not mention lower memory consumption on purpose because current application servers already provide lazy loading. For one application server with one application in it, the Enterprise Fatjar can save about 20 MB of RAM. But with a second application running on the same environment, the application server clearly wins, since running another fatjar requires the machinery to be started in a separate JVM. However, there are also some other downsides:
- Size: both in-memory and on HDD.
- Running one runtime per environment is no longer possible. One enterprise app = one runtime.
- Loading the application server components only once per machine is no longer possible.
- The build may become dependent on the fatjar solution, making it hard to migrate back.
- Generally no fast redeployments without additional tools, adding complexity to the project.
Enterprise Fatjars, as already mentioned, have pieces of the application server inside. There is no magic, and the functionality formerly bundled in the application server did not disappear, it is now only bundled inside.
Spring as a Special Case
A special case is Spring Framework. They call their Enterprise Fatjar Spring Boot. But before Spring Boot came along, Spring itself was kind of special. Spring does not use all the functionality present in Java EE application servers, but commonly uses only parts of Java EE specs. And that part is rather small. The way Spring works is simple — basic Java EE JSRs are taken (servlets, for example) and there is a large amount of non-standard functionality added to it. When a typical Spring application is started (is deployed into an application server), only a very small number of the standard libraries were used. The rest was started from scratch with each Spring application deployed into the application server.
For this reason, it was, and still is, popular to deploy Spring into so-called servlet containers (like Tomcat). Containers, unlike servers, provided a much smaller subset of Java EE specs. In fact, just enough for Spring to run and not much more. This makes perfect sense for Spring Framework developers.
All the libraries were commonly present in the Java Web Archive (.war). The resulting WARs were huge. A common size was 100+ megabytes for real-world projects, 200+ or even 500+ megabytes for extreme ones made by incompetent people. Such extremely big archives (500+) are no fault of Spring, these are just the results of general incompetency among developers. It serves as a perfect example of what happens if Average Joe Developer is given the chance to include all the dependencies on his/her own. It ends in disaster. Therefore, tools like Spring Boot are necessary because those tools come with “opinions.” In the real world, the keyword “opinionated” is quite common, and for me, it is a polite way of saying that it does what average developers in software houses are not generally capable of — keeping things tidy. In the case of Spring, Boot really helped a by forcing tidiness.
And then, the question arises. Why is a common application server needed, when in fact 95% of the libraries are run again and again with each new Spring application deployed? The answer is obvious: It is not needed. So why bother? Bundling the small but crucial pieces of Java EE specs Spring might require to run into the JAR does not do a lot of harm. In fact, there is one upside to it. Different Spring applications carried different versions of the same libraries throughout history, and there were classloading issues all the time when the applications were deployed to the very same application server running in the very same JVM. Fatjars solve this problem, and many more, but add a little bit of overhead. I must say, the overhead is considered negligible by many, and truth be told, RAM and HDD space are extremely cheap.
Spring is a special case, and creating an EFJ does little harm with Spring, as the obvious damage may be done just by using it, and the advantages of running one application server are gone. When I’m talking about damage being done, I only mean the use case where several applications can run on one machine, under one JVM easily. Also, the fatjar builds need conversion when developers want to get back to WAR packaging.
Java EE and Enterprise Fatjars
In the pure Java EE world, we provide application servers as an opinionated bundle aimed at providing the necessary plumbing. It should “just work.” You can grab an all-in-one package application server, or you can build one on your own. The main point is that the all-in-one package is the default way. In the Java EE world, building the container part-by-part is commonly called “microcontainers.” There are many of those:
In reality, almost every application server out there can do it. The idea is always the same — to create one single executable JAR with all the dependencies inside.
Java EE fatjar solutions are very advanced in terms of the possibilities. A nice API for deployment using pure Java code is one of those cool possibilities. Personally, I have yet to face the use case for this technique besides testing. For example, WildFly Swarm supports usage of ShrinkWrap not only to deploy, but to create the archives. Payara Micro also supports in-code deployment. And other cool stuff, like direct deployment from Maven repositories. Or, deployment using a command line.
When these ways of redeployment are used, some of the main disadvantages are eliminated:
- A single JVM per many deployment units is started (real memory saving).
- “Archive scanning” is active — exactly the libraries required by the application are loaded.
A Payara example looks as simple as this:
Deployment using an application server JAR is also possible. Multiple deployment units can be deployed at once. Without problems.
One big plus of Java EE enterprise fatjars is the creation process. It is non-intrusive. The resulting JAR can be created as an additional step to the original build process. The application itself does not even know it is built as a part of a fatjar solution. This leaves all the doors open for you as a developer — the option to deploy to a standard application server is still there without any additional tweaks. Let’s take Payara as an example.
This creates an “Enterprise Fatjar” using Payara Micro from the original WAR. No actions needed. It can be automated with Gradle or Maven with ease.
Lazy loading libraries into memory is standard for every microcontainer out there, including Payara. And like in the case of Spring Boot, developers using WildFly Swarm can choose their own application server parts present in the resulting fatjar using a generator. Payara always includes “everything” currently.
Horizontal Scaling and Docker
Nowadays, there are slightly different priorities than in the not-so-distant past. For a long time, arguably, the trend has been to make the applications stateless to enable potentially massive horizontal scaling. Applications are now built as server-invariant. Spawning instances on-demand is the selling use case for many. The traditional approach of hosting apps directly on VMs suffers from several disadvantages, such as the overhead of a full OS and slow start-up time, a degree of vendor lock-in, and relatively coarse-grained units for scaling (source).
How is this in any way related to Java, Spring, Spring Boot, Java EE, or fatjars? Spawning instances on-demand as a part of horizontal scaling strategy leads to one observation:
Every single application, or service in modern terms, has different requirements for scaling. Since every application/service is scaled independently, each instance spawned requires an independent runtime environment.
A modern way to do this is Docker. Each application has its own Docker image with all the dependencies. Docker and its tooling provide service discovery and load balancing out of the box. This leads to less use for one of the big advantages of an application server — shared libraries loaded into memory just once for many applications. Some services are quick to serve many requests and, therefore, require fewer running instances. Some require more. When each application/service is spawned, a new Java EE application server is also spawned. Putting multiple applications into one application server in this environment becomes an anti-pattern. However, a few years ago, size used to be a serious topic, as demonstrated by Arun Gupta. Now it seems we do not care anymore. Or do we?
Are Application Servers Dead? And Did Docker Kill Them?
The opposite is the truth. The application server is the materialization of many valuable ideas:
- Used by many. Bugs are quick to be revealed and fixed.
- Support available.
- Well documented.
- Standards compliant. Well-tested and certified.
- Many options — no vendor lock in.
And potentially many others. These advantages are valid for both microcontainers bundled in fatjars and for traditional application servers. In fact, fatjars do not impact these ideas. Simply put, microcontainers are just a curated subset of original features. Since Docker is used, there is no general need to create fatjars whatsoever. The fatjar has one advantage — only the required libraries are bundled, therefore HDD space is saved. This is especially true for Spring Boot or WildFly Swarm. This is not the case with memory, since every application server nowadays uses lazy loading of libraries. In fact, the disk space overhead of Docker itself is bigger than the difference between Payara and Payara Micro, or Wildfly Web profile vs. Wildfly Swarm.
|Solution||HDD Space||Memory drained||Code used|
|Payara 171 Full||119 MB||48 mb||GitHub JAX-RS sample|
|WildFly 10.1.0 standalone||139 MB||43 MB||GitHub JAX-RS sample|
|WildFly Swarm||45 MB||38 MB||GitHub Swarm Sample|
|Payara Micro||62,6 MB||37 MB||GitHub JAX-RS sample|
|Payara Micro Fatjar||62,2 MB||37 MB||GitHub JAX-RS sample|
|Spring Boot MVC REST||15 MB||28 MB||GitHub Spring Rest example|
|TomEE Web profile||34 MB||17 MB||GitHub JAX-RS sample|
All memory measurements are done after forced garbage collection. Measured with VisualVM. Feel free to grab the code and reproduce the results. What was measured was no real use case. Usually, applications use much more than a single REST API endpoint without any transaction management, dependency injection, thread management, pooling, timers, etc. But from the results and from the content of resulting packages, it can be seen that Java EE application servers bundle more functionality out of the box. With Spring, there is less functionality in a very basic application, therefore less space on HDD and also in memory. But please note that there is, for example, no JPA support. This showcase, however, demonstrated the very basic overhead.
The overhead of starting a full-blown Java EE application server is exceptionally low compared to other solutions. The HDD space cost is negligible. The lazy loading seems to work very well. The difference between a fatjar and an application server with the very same application deployed is about 10 MB. 20 MB in the worst case. Do we need to care about this in 2017? I do not think so. Especially considering the fact that there is no administration console in the “Enterprise Fatjars.”
Using application servers inside Docker containers introduces negligible overhead, but that allows us to ship just the business logic without the need to fine-tune the dependencies every time the application changes. There is only one single dependency by default, and that is the Java EE 7 API. Nothing more.
There are official Docker images for all the major players:
And many others. Feel free to search DockHub on your own.
Creating a Dockerized Java EE application is then a matter of a few lines in a Dockerfile. For example, with WildFly:
Fast Redeployments With Java EE and Docker
Running an application server has incredible redeployment times because only the basic application with the business logic is changed. All the rest is running and does not need to be started again. This is great for drop-in production redeployments and even better for development. Fatjars, without additional tooling, must be started over and over again with every change. For real-world applications, the time for the application to start might be tens of seconds, maybe more. However, if just the business logic is swapped, a few (up to hundreds of) milliseconds are usually required and the changes can be seen instantly. And it can be done without additional tooling — only the basic tools that come with the operating system are needed: copying a file. For example, Spring introduced additional tooling to solve this fatjar issue. An issue Java EE thin WARs never had, so there is nothing to solve.
With Java EE, fast redeployments are easy. Thanks to Docker volumes, the redeployment can be done in no time. Without additional tools. All the application servers support deployment by copying the archive into a specific deployment folder. With Docker, the deployment folder can mapped to a folder of the host filesystem. By placing the WAR into this specific folder, deployment happens. Rewriting the archive in that folder means redeployment. Easy and done instantly.
Of course, the /home/pavel/deployments is my example folder :). Because the server is already started and the WAR contains only business logic, on my laptop it takes < 200 ms to redeploy. Bigger WARs (3,5 MB JSF project ) take < 1 sec. The redeployment can be automated by writing a simple script or making it part of the build process. It can be done in Ant, Maven, and Gradle, and IDEs also support it. It is still only a simple file copy. Point is, it is independent and no specialized tools are required. In Gradle, it can be achieved with a very simple task.
Overall, Enterprise Fatjars in pure Java EE do not bring critical in-memory savings. On the other hand, they introduce big HDD overhead. This overhead has now, however, become a standard pattern thanks to Docker and the general shift to stateless, horizontally scalable applications. The presented solutions are derived from existing application servers and, therefore, present the same curated set of libraries. The whole fatjar functionality is added on top of Java EE standards without any interference.
A major finding is simple: Docker is the way to pack Java EE applications, not Enterprise Fatjars. The application server memory overhead is just a few megabytes more, but everything is there and there is only one dependency. Redeployments are really fast, making development easier.
Some fatjar use cases, such as programmatic deployment, support for ShrinkWrap, console deployment, and exact control of the JSRs included in the application are therefore something worth more attention. It's the ability to decide, in-code, which applications will be deployed and when they might just be something you may need in several rare use cases. Spring is a different story. Fatjars are now Spring Framework’s way to deliver a curated set of runtime-required libraries. In the Spring ecosystem, it really helps, and since there are few Java EE specs required by Spring (if any ?), deploying to Java EE application servers makes little sense. I see Spring Boot as an obvious evolution of Spring. With all the pros and cons mentioned.
The path we now go is a path of little reuse, but it seems like we do not have to care anymore because there are plenty of resources. And Java EE fits into this environment just fine.
For Additional Learning
To explore more, following links are recommended
Opinions expressed by DZone contributors are their own.