Java fat jars

In a world where agility matters, the pursuit to reduce wasted time in environment configurations is apparent in many technologies. Some techniques, such as Virtual Machines, that enable distribution of pre-configured images have existed for decades, while others like Linux containers are more recent.

Even platforms like Java allow developers to package all dependencies, resources and configuration files in single JAR (Java Archive) file. What started initially as way to have executable Java classes in Java SE (Standard Edition), has now gained notoriety also in the Enterprise. The promise to deliver runnable servers in a "fat-jar" that contains not only your application, but also the server runtime and its resources (libraries, datasources, transaction configuration, etc); made projects like WildFly Swarm, Spring Boot and Vert.x become very popular in "Java land".

Although these projects allow the "packaging" of the server runtime, an elastic environment like "Cloud computing" stands in need of another "layer" of wrapping, and Linux containers are perfect for it. When you wrap your "fat-jar" in a container, you can also provide a custom execution environment for you JAR file that provides an Operational System, the Java Virtual Machine, and it can also be enriched with JMX (Java Management Extensions) that enable easy monitoring of the JVM. You can also set configuration flags that enable debugging, etc.

 

The solution

For Linux containers, one method for running "fat-jars" is to use fabric8/java-jboss-openjdk8-jdk as a base docker image. This image provides Open JDK 1.8 and a startup script (run.sh) that enables a Jolokia agent --- a remote JMX with JSON over HTTP --- and also allows the use of environment variables to modify the behaviour of the JVM according to what is determined by 3rd factor of the "The twelve-factor app": Store config in the environment

Let's take, for example, the "hello world" WildFly-Swarm microservice called "hola" that returns "hello world" in Spanish. The source-code for this application is available on Github, here: https://github.com/redhat-helloworld-msa/hola.

Note: For a complete MSA (Microservices Architecture ) example that integrates technologies like WildFly Swarm, Spring-boot, Vert.x and NodeJS, browse the documentation available at:  https://github.com/redhat-helloworld-msa/helloworld-msa

Note that the Dockerfile of the "hola" app is really simple. You just need to set the name of your JAR file in the JAVA_APP_JAR environment variable, and the startup script will take care of the rest:

FROM fabric8/java-jboss-openjdk8-jdk:1.0.13

ENV JAVA_APP_JAR hola-swarm.jar
ENV AB_OFF true

EXPOSE 8080

ADD target/hola-swarm.jar /app/

Note: The environment variable AB_OFF=true disables the use o Jolokia. We need it for WildFly-Swarm fat-jars due to a known bug.

This base image also allows you to customise the startup of the Java process with many other environment variables that you can see in the project readme file. Here are some examples:

  • JAVA_OPTIONS - options to add when calling java
  • JAVA_MAIN_CLASS - A main class to use as argument for java.
  • JAVA_APP_JAR - A jar file with an appropriate manifest so that it can be started with java -jar if no $JAVA_MAIN_CLASS is set. In all cases this jar file is added to the classpath, too.
  • JAVA_APP_NAME - Name to use for the process
  • JAVA_CLASSPATH - the classpath to use. If not given, the script checks for a file ${JAVA_APP_DIR}/classpath and use its content literally as classpath. If this file doesn't exists all jars in the app dir are added (classes:${JAVA_APP_DIR}/*).
  • JAVA_ENABLE_DEBUG - If set remote debugging will be switched on
  • JAVA_DEBUG_PORT - Port used for remote debugging. Default: 5005

After you create a Docker image with the command "docker build -t redhatmsa/hola ." you will able to run a container using Docker, Kubernetes and Openshift.

Docker

Execute:

$ docker run redhatmsa/hola

Isn't that simple?

But how do you change the container configuration? The environment variables need to be set before the execution of the container, so if you want to execute this application with 1GB of Heap memory, you can simply do:

$ docker run -e JAVA_OPTIONS=-Xmx1g redhatmsa/hola

Kubernetes

The same image can be used inside Kubernetes. To run the container with a 1GB Heap, execute:

$ kubectl run hola --image=redhatmsa/hola --env=JAVA_OPTIONS=-Xmx1g

Openshift

A similar command to deploy this image in Openshift will be:

$ oc new-app redhatmsa/hola -e JAVA_OPTIONS=Xms1g

An extra tip for Openshift: If you want to update the configuration, just update the environment variable in the "hola" Deployment Config. The Deployment Config will take care to replace all running Pods by new ones with the new configuration.

$ oc env dc/hola JAVA_OPTIONS=-Xmx200m

Conclusion

Instead of hardcoding a "CMD java -jar <you-jar-file>" inside a Dockerfile, the use of the fabric8/java-jboss-openjdk8-jdk as a base docker image allows you to use an existing JDK 1.8 runtime with externalized JVM configurations --- the Dockerfile for your application becomes extremely simple. Features like remote debugging and monitoring can be easily enabled without needing to modify your existing image.

For more information related to containers (and much more), register today in the Red Hat Developers website.

About the author:

Rafael Benevides is a Director of Developer Experience at Red Hat. In his current role he helps developers worldwide to be more effective in software development, and he also promotes tools and practices that help them to be more productive. He worked in several fields including application architecture and design. Besides that, he is a member of Apache DeltaSpike PMC - a Duke’s Choice Award winner project. And a speaker in conferences like JUDCon, TDC, JavaOne and Devoxx. Twitter | LinkedIn | rafabene.com

Last updated: March 15, 2023