Enterprise Java

Fatjars, Thinwars and why OpenLiberty is cool

Fatjars

Building a Fatjar (or Uberjar) that contains everything you need to run your application nicely packaged together means you can just do:

java -jar myapp.jar

and off you go. No Application server. No classpath.

This approach has been popularised by the microservices architectural style and frameworks like Springboot.

“In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery”.

Having a bunch of executable jar files tick all the boxes above.

Java EE

The fatjar concept has also been available in Java EE for a while now. All the lightweight application servers have a “Micro” option:

There are many pros to having a fatjar deployment. However, there are some cons as well.

  • You make this choice at development time (and it’s actually a deployment time choice). I like to separate my development model from my deployment model.
  • This gives you a sub-optimal development cycle. You need to build a fatjar, then stop the previous version, most likely with a kill, then do a start again. The turnaround from “code change” to “code running” gets annoyingly long after a while. One of the benefits of deploying a thin war to a running application server is the quick turnaround.
  • Not having a classpath is actually a pro and a con. Even though one of the advertised advantages of fatjars is not having an application server, you actually still have an application server, it’s just embedded. Having only one jar file, means your application and the embedded application server have the same dependencies. You might run into issues where your application uses another version of a dependency than the embedded server. This can cause some nice hidden bugs. Having the ability to isolate the application server classpath from you application is actually a good thing. Java 9 might solve this, however most application servers are still running on Java 8.

Docker

Docker has taken the microservices approach a level deeper and allow you to isolate on OS level. This means, building separate jar files become less relevant as you will be building separate Docker images.

Building a fat jar to be deployed as a Docker image is actually slower and heavier than a thin war. You typically layer you Docker images:

(above: your final layer in the fatjar option is much heavier than the thinwar option, as it includes the embedded application server)

OpenLiberty is cool !

Traditional Websphere is big, slow, expensive and difficult to install. Not something you would use to build Microservices with. IBM is a fairly late entry to the lightweight application server solutions with Websphere Liberty, of which the core has been open-sourced recently under OpenLiberty.

But this late entry might be the reason why they have done certain things right and very clean. The way that you can only load the parts that you need with features, and how you can extend the server with your own features, is awesome. Even though other application servers are also doing some modularity with OSGi (or JBoss Modules), it’s just easier with Liberty. For Liberty, including Microprofile is just another feature. Other application servers have added MicroProfile to their fatjar (“Micro”) distributions, and even though I believe it’s possible to also add it to the full application server release, it’s not easy to do.

The other cool thing is how you can very easily decide the deployment model only at deploy time. So you can have the best of all worlds. You can develop against a full application server with the thinwar model to get a quick turnaround. When building, you can assemble a fatjar, thinwar, docker image or all of them. What you develop against stays the same.

OpenLiberty with MicroProfile example

I have created a simple application to demonstrate these deployment options. (Code is available in GitHub)

I did not want to build a basic “Hello world”, as I wanted to include some of the MicroProfile features, so this is a “Quote of the Day” app. It uses a factory to load a quote provider (there is only one for now). The current provider gets and caches a quote from forismatic.com. I use the MicroProfile Configuration API to configure things like the HTTP Proxy, the URL and the provider to load. I use the MicroProfile Fault Tolerance API to make sure we survive when the provider source is not available.

Configuring OpenLiberty

Configuration on OpenLiberty is also very clean. This makes it easy to include the configuration in your project. Using maven resource filtering, you can also extract certain variables to your build. Below the important parts of my server.xml (you can see the full one in github)

src/main/liberty/config/server.xml

<?xml version="1.0" encoding="UTF-8"?>
<server description="${project.build.finalName}">

    <featureManager>
        <feature>javaee-7.0</feature>
        <feature>microProfile-1.2</feature>
    </featureManager>

    <httpEndpoint id="defaultHttpEndpoint"
        httpPort="${httpPort}"
        httpsPort="${httpsPort}"/>

    <application location="${project.build.directory}/${project.build.finalName}.war"/>

    <logging traceSpecification="${log.name}.*=${log.level}"/>

</server>

For now we just include the umbrella features for Java EE and Microprofile. A bit later we can fine tune that to reduce the memory footprint.

The ${httpPort} and ${httpsPort} will actually come from bootstrap.properties that we create with the liberty maven plugin.

All variables in the server.xml, including ${project.build.directory} and ${project.build.finalName} will be replaced when we build with this resource filtering in the pom.xml:

<build>
    <finalName>${project.artifactId}</finalName>
    <resources>
        <resource>
            <directory>${basedir}/src/main/liberty/config</directory>
            <targetPath>${project.build.directory}</targetPath>
            <filtering>true</filtering>
            <includes>
                <include>server.xml</include>
            </includes>
        </resource>
    </resources>
</build>

(You can see the full pom.xml in github)

So when we do a mvn clean install the server.xml will be copied to the target directory with the variables replaced.

Deployment options

I am using maven profiles to allow me to select, at build time, which deployment option I want:

In the <build> of pom.xml

<plugins>
    <plugin>
        <groupId>net.wasdev.wlp.maven.plugins</groupId>
        <artifactId>liberty-maven-plugin</artifactId>
        <version>${openliberty.maven.version}</version>

        <configuration>
            <assemblyArtifact>
                <groupId>io.openliberty</groupId>
                <artifactId>openliberty-runtime</artifactId>
                <version>${openliberty.version}</version>
                <type>zip</type>
            </assemblyArtifact>
        </configuration>
    </plugin>
</plugins>

On the fly option

This option follows the same development cycle than a fatjar cycle (although it does not create a jar file). If you do a mvn clean install -Pfatjar, it will install, configure (from server.xml) and start a server in the foreground. In other words, the mvn process does not finish, as the server start is part of the mvn process. To stop the server you need to ctrl-c the process.

<profile>
        <id>fatjar</id>
        <activation>
            <property>
                <name>fatjar</name>
            </property>
        </activation>
        <build>
            <plugins>
                <plugin>
                    <groupId>net.wasdev.wlp.maven.plugins</groupId>
                    <artifactId>liberty-maven-plugin</artifactId>

                    <executions>
                        <execution>
                            <phase>install</phase>
                            <goals>
                                <goal>install-server</goal>
                                <goal>create-server</goal>
                                <goal>run-server</goal>    
                            </goals>

                            <configuration>
                                <configFile>${project.build.directory}/server.xml</configFile>
                                <bootstrapProperties>
                                    <httpPort>${openliberty.http.port}</httpPort>
                                    <httpsPort>${openliberty.https.port}</httpsPort>
                                </bootstrapProperties>
                                <jvmOptions>
                                    <param>-Xmx${openliberty.Xmx}</param>
                                </jvmOptions>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>

            </plugins>
        </build>
    </profile>

Of course using an IDE like Netbeans (or any other IDE) this is actually just a button you click:

Full application server option:

With this option we want to install, configure and start the server, and then deploy a thin war continuously as we write code. We still install and configure the server from scratch every time we start the server, just not on every deploy.

mvn clean install -Pstart-liberty will install, configure (from server.xml) and start a liberty server in /tmp folder:

<profile>
        <id>start-liberty</id>
        <activation>
            <property>
                <name>start-liberty</name>
            </property>
        </activation>
        <build>

            <plugins>
                <plugin>
                    <groupId>net.wasdev.wlp.maven.plugins</groupId>
                    <artifactId>liberty-maven-plugin</artifactId>

                    <executions>

                        <execution>
                            <id>1</id>
                            <phase>pre-integration-test</phase>
                            <goals>
                                <goal>install-server</goal>
                            </goals>
                            <configuration>
                                <assemblyInstallDirectory>${openliberty.installDir}</assemblyInstallDirectory>
                            </configuration>
                        </execution>

                        <execution>
                            <id>2</id>
                            <phase>pre-integration-test</phase>
                            <goals>
                                <goal>create-server</goal>
                                <goal>start-server</goal>
                            </goals>
                            <configuration>
                                <installDirectory>${openliberty.installDir}/wlp</installDirectory>
                                <serverName>${project.artifactId}</serverName>
                                <configFile>${project.build.directory}/server.xml</configFile>
                                <bootstrapProperties>
                                    <httpPort>${openliberty.http.port}</httpPort>
                                    <httpsPort>${openliberty.https.port}</httpsPort>
                                </bootstrapProperties> 
                                <jvmOptions>
                                    <param>-Xmx${openliberty.Xmx}</param>
                                </jvmOptions>
                            </configuration>
                        </execution>

                    </executions>
                </plugin>

            </plugins>
        </build>
    </profile>

You can now deploy the thinwar continuously:

mvn clean install -Pdeploy

<profile>
    <id>deploy</id>
    <activation>
        <property>
            <name>deploy</name>
        </property>
    </activation>
    <build>
        <plugins>
            <plugin>
                <groupId>net.wasdev.wlp.maven.plugins</groupId>
                <artifactId>liberty-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>deploy</goal>
                        </goals>
                        <configuration>
                            <appArchive>${project.build.directory}/${project.artifactId}.war</appArchive>
                            <serverName>${project.artifactId}</serverName>
                            <installDirectory>${openliberty.installDir}/wlp</installDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</profile>

Stopping the server is also very easy:

mvn clean install -Pstop-liberty

Fatjar distribution

It’s very easy to create a fatjar distribution with mvn clean install -Ppackage-liberty:

<profile>
    <id>package-liberty</id>
    <activation>
        <property>
            <name>package-liberty</name>
        </property>
    </activation>
    <build>
        <plugins>
            <plugin>
                <groupId>net.wasdev.wlp.maven.plugins</groupId>
                <artifactId>liberty-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>package-server</goal>
                        </goals>
                        <configuration>
                            <packageFile>${project.build.directory}/${project.artifactId}.jar</packageFile>
                            <include>runnable</include>
                            <serverName>${project.artifactId}</serverName>
                            <installDirectory>${openliberty.installDir}/wlp</installDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</profile>

In my target directory I now have an executable (fat)jar file that I can start with: java -jar quote-service.jar

In all the above mentioned profiles you can test the example app with:

mvn -Dtest=com.github.phillipkruger.quoteservice.QuoteApiIT surefire:test

And that should give you a quote of the day:

{
    "author":"Naguib Mahfouz",
    "text":"You can tell whether a man is clever by his answers. You can tell whether a man is wise by his questions."
}

Fine-tuning the memory footprint.

To start off I used the umbrella javaee-7.0 and microProfile-1.2 features, even though my application only uses a subset of these specifications.

Using jconsole I measured the memory footprint (after a GC) of the running server:

50,691 kbytes

You can change the server.xml to only include the features your app is using, in my example:

<feature>jaxrs-2.0</feature>
<feature>ejb-3.2</feature>
<feature>cdi-1.2</feature>
<feature>jsonp-1.0</feature>
<feature>jaxrsClient-2.0</feature>
<feature>mpConfig-1.1</feature>
<feature>mpFaultTolerance-1.0</feature>

Again using jconsole I measured the memory footprint (after a GC) of the running server:

30,198 kbytes

Ideally you would also fine-tune the pom.xml to only include the specification you use.

Conclusion

We can argue whether it’s better to do fatjars vs thinwars and the pros and cons of having an application server, or not. However, not having to make this decision when we start developing (ie. download the micro distribution or the full distribution) but only when we build, allows for more options. Maybe it’s possible to do that with the other application servers, but OpenLiberty made it easy.

More info

Also read this great blogs from Pavel Pscheidl

and watch this video from Adam Bien

Published on Java Code Geeks with permission by Phillip Krüger, partner at our JCG program. See the original article here: Fatjars, Thinwars and why OpenLiberty is cool.

Opinions expressed by Java Code Geeks contributors are their own.

Phillip Kruger

Phillip is a software developer and a systems architect who knacks for solving problems. He has a passion for clean code and evolutionary architecture. He blogs about all technical things.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button