Modernize and optimize Spring Boot applications

About this article

This article presents a simple technique to optimize, modernize, and containerize Spring Boot applications. The technique uses two docker files in a unique way to containerize and transform any Spring Boot application. The transformed application is much more suitable for an agile development environment where frequent application changes take place and only a small amount of data will be pushed to an internal registry for such changes. The footprint and the throughput performance of the transformed application are significantly better than those of the original one. The technique is amenable to automation.


Spring Boot applications are quite popular among users in creating web and stand-alone Java applications. Spring Boot applications are self-contained — all the application dependencies and requirements including the middleware server are packaged in a Spring Boot jar. The embedded middleware server for Spring Boot applications is typically Tomcat; however, it can be Jetty, Undertow, or Liberty, too.

The self-contained standalone nature of Spring Boot applications makes them easy to build and easier to deploy. There is no need to check and provide dependent libraries in the class path, or even to deploy the application in a middleware server. The Spring Boot jar artifact will run in any machine which provides a JVM. Spring Boot is very easy to containerize with simple docker files to run them in ‘Container as a Service’ (CaaS) environments. This self-contained nature is perhaps the principal reason behind the popularity of Spring Boot.

The simplicity arising out of the self-contained Spring Boot comes at a price. A Spring Boot jar is large. It is an uber jar, containing the main application packaged with all its dependencies and even a middleware framework. It’s corresponding container image containing the JRE along with the uber jar will also be large. In fact, a Spring Boot uber jar may not be very convenient to work with in a CaaS environment where typically the application versions get generated and deployed in an agile fashion on a continuous integration and continuous deployment (CI/CD) pipeline.

A change in the application code will result in the rebuild of the entire Spring Boot uber jar even though Spring and other dependent libraries change rarely. The entire docker layer containing the uber jar will be pushed to the container registry on every application code change and will also be pulled from the registry while starting new containers. This can be very expensive in a typical agile environment in terms of registry space, container build time, and the large number of bytes that get pushed through the network. Refer to the following blogs for good discussions:

Along with the ‘push size’ of the container images, typically in a CaaS environment, users also pay attention to short startup time, low memory footprint and high throughput performance for their containers. Faster startups help in achieving efficient elasticity which is essential in a cloud environment. A low memory footprint conserves the hardware resources, which may allow higher packing density in a cloud. Lower memory footprint is also especially important in development environments which are typically constrained in resource consumption. A high throughput performance is essential for good load handling capabilities of the containers.

Primarily to alleviate the container image push size issues in a CaaS environment and also to reduce memory footprint size, we formulated and experimented with a simple strategy for modernizing and optimizing Spring Boot applications. Simply stated, in abstraction, our technique accepts a Spring Boot uber jar and methodically transforms it into a container image that has very low push size for application changes and very low memory footprint, with excellent startup time and throughput performance, while keeping the original functional semantics intact.

Strategy for modernizing Spring Boot applications

The transformation technique mentioned in the introduction hinges on creating two docker files for building the final container image from a given Spring Boot uber jar:

  • The first docker file creates a ‘base image’ consisting of Open Liberty middleware and an OpenJDK Java11 built with Eclipse OpenJ9. To learn more about OpenJ9, read the article “Eclipse OpenJ9; not just any Java Virtual Machine” (Eclipse Foundation, April 2018).
  • The second docker file splits the original uber jar to Spring libraries and the core application and lays them over the ‘base image.’

In the above mentioned docker files, we take great care in minimizing the overall image, push size and startup time of the transformed Spring Boot application. For minimizing the ‘base image’ we use the composability features of Open Liberty and JRE 11. While creating the base image, we include only the features of Open Liberty and the modules of the Eclipse OpenJ9 based OpenJDK JRE11 which are needed to execute the target application.

We used the Shared Classes Cache (SCC) feature of OpenJ9 with ahead of time (AOT) compiled code and pre-processed Java classes to speed up the container startup. The SCC is stored as a memory mapped file and it can be packaged into a container image during the build process. Classes that are loaded by the built-in class loaders in Java (e.g. system classloader) or any user classloader that extends URLClassLoader are eligible for being stored in the SCC. The first docker file populates SCC.

In the second docker file, we separate out the application jar from the self-contained Spring Boot uber jar. First the Spring libraries and other third party libraries, if any, followed by the ‘thinned’ application jar are added to the ‘base image’ to produce the final image. This final step greatly reduces the push size for application changes. In fact, in most cases, the push size will be the same as that of the application jar which are typically at most a few MBs in size.

Detailed steps

In this section, we discuss the above mentioned transformation strategy in detail. We break up the entire process into two phases corresponding to the two docker files. In each phase we go over the individual steps, which are often illustrated with relevant portions of the docker files.

In the following and also in the rest of the article, we have used Petclinic, the open source application, which is very popular in Spring communities, as our running example to illustrate the transformation strategy under consideration. It should be noted that all the results and the techniques described in the article are equally applicable to any Spring Boot application.

Phase 1. Base image construction

This section goes over the steps used in creating the base image.


Create minimized JRE

To include only the Java components needed to run the application in the final container image we need to determine the required modules.

We used the jdeps command of JRE to determine list of Java components somewhat like the following:

jdeps --list-deps  <Spring Boot uber jar>

In our case we used spring-petclinic-2.1.0.BUILD-SNAPSHOT_openliberty.jar, the jar corresponding to the Petclinic application as the parameter to the jdeps command.

Note: Unfortunately, the present jdeps command seems to frequently provide incomplete lists of the required Java components to execute applications. We used it as a starting point to get the initial list of modules. We added a few more in an iterative way as we encountered exceptions in the application build process. As it stands now, to arrive at a minimally required list of Java modules, one may have to keep adding modules to the initial list as long as exceptions are encountered because of missing modules during the actual execution of the application. Note that, to arrive at a required list of Java modules there is no need to containerize the application, straightforward executions in non-containerized form will suffice.

For the Petclinic sample, the list of the required Java modules is constructed after a few iterations as: java.desktop, java.sql, java.naming,, java.instrument,, openj9.sharedclasses, jdk.unsupported, java.rmi.

Once we have the complete list of modules, we use the jlink tool to create a customized OpenJDK-OpenJ9 JRE11 containing only the required java modules needed to execute the Petclinic application. Typically, the use of the jlink tool results in significant reduction in Java rumtime sizes for applications.

For additional information about jlink, read the article “Using jlink to Build Java Runtimes for non-Modular Applications” (Medium, October 2018).

FROM adoptopenjdk/openjdk11-openj9 as buildjre

# Argument for file containing the list of java_modules

#### copy in application to get better populated SCC
COPY --chown=1001:0 ${JAVA_MODULES} /tmp/java_modules.txt

# create the minified JRE using jlink
RUN /opt/java/openjdk/bin/jlink --no-header-files \
           --no-man-pages --compress=2 \
           --strip-debug \
           --add-modules $(cat /tmp/java_modules.txt) \
           --output /opt/petclinic-jdk11-minified

Create minimized Open Liberty

To reduce the size of the container image, we create a minimized Open Liberty package containing the features needed to run the modernized Spring Boot application using the package command with -include=minify option.

FROM open-liberty:kernel-java11 as buildLiberty_minify

# Install unzip; needed to unzip Open Liberty
USER root
RUN apt-get update \
    && apt-get install -y --no-install-recommends unzip
USER 1001

# Copy in a server.xml to start server with correct features set
COPY --chown=1001:0 petclinic/server.xml /opt/ol/wlp/usr/servers/defaultServer

# Create a minified openliberty package for use in final image
RUN /opt/ol/wlp/bin/server package --archive=/tmp/ --include=minify --os=Linux

# Unzip the minified open liberty
RUN unzip /tmp/ -d /tmp/ol

It should be noted that Open Liberty server comes with a default server.xml configuration file. As shown above, in the docker file for creating the base image, we replace the default configuration with a server.xml file, which includes only the following features needed to run the Petclinic Spring Boot application:


The server.xml file also specifies the Spring Boot jar file:

<springBootApplication location="spring-petclinic-2.1.0.BUILD-SNAPSHOT_openliberty.jar"/>

Populate SCC

We populate the shared class library to speed up the container start process. To populate the SCC:

  1. Get Open Liberty kernel image with OpenJDK-OpenJ9 JRE11 from docker hub.
  2. Copy the Spring Boot uber jar into the apps location of Open Liberty.
  3. Copy the server.xml to the defaultServer location of Open Liberty.
  4. Set the JAVA_HOME environment variable to the minimized OpenJ9-based OpenJDK JRE11 created earlier.
  5. Start and stop the defaultServer application server to populate the SCC.
FROM open-liberty:kernel-java11 as buildscc


#### copy in application to get better populated SCC
COPY --chown=1001:0 ${JAR_FILE} /opt/ol/wlp/usr/servers/defaultServer/apps/spring-petclinic-2.1.0.BUILD-SNAPSHOT_openliberty.jar

#### start server with correct features
COPY --chown=1001:0 petclinic/server.xml /opt/ol/wlp/usr/servers/defaultServer

### move over the minified java11 jre
COPY --chown=1001:0 --from=buildjre /opt/petclinic-jdk11-minified /opt/petclinic-jdk11-minified

ENV JAVA_HOME=/opt/petclinic-jdk11-minified

#### Starting the server will populate the SCC
RUN /opt/ol/wlp/bin/server start  \
    && /opt/ol/wlp/bin/server stop

Note that, for the sake of simplicity, to populate the SCC, we use the original uber jar and not the actual thinned application jar, since we found no benefit in using the thinned application jar over uber jar in populating the SCC. We also use the downloaded Open Liberty and minimized OpenJDK-OpenJ9 JRE11 to populate SCC. It should be mentioned that we copy the server.xml file to the defaultServer location for properly populating the SCC and not for creating the final container image.


Create base image

From the above mentioned components, we create the base image by layering the following components in the order shown below:

  • Base operating system (UBI in this case)
  • Minimized OpenJDK-OpenJ9 JRE11
  • Minimized Open Liberty
  • Shared class library
### Build a base image used later when only the app gets updated

### Get the ubi

### copy in the minified java11 jre
COPY --chown=1001:0 --from=buildjre /opt/petclinic-jdk11-minified /opt/petclinic-jdk11-minified

## copy in the minified package of open liberty
COPY --chown=1001:0 --from=buildLiberty_minify /tmp/ol /opt/ol/

## copy in the populated SCC from build_scc
COPY --chown=1001:0 --from=buildscc /opt/ol/wlp/output/defaultServer/.classCache/C290M11F1A64P_liberty_G37 /opt/ol/wlp/usr/servers/.classCache/C290M11F1A64P_liberty-root_G37

# Set the server.xml so the server will start with the correct features
COPY --chown=1001:0 petclinic/server.xml /opt/ol/wlp/usr/servers/defaultServer

# Setup the JAVA_HOME environment variable
ENV JAVA_HOME=/opt/petclinic-jdk11-minified

The ordering of the layers while creating the image is important. The layers should be ordered according to their ‘expected rate of change’ — the more permanent layers should be placed ahead before the layers which may change with higher frequency.

As the very final step in this phase we build the base docker image

docker build --no-cache -f dockerfile_spring_petclinic_minijre_wscc --build-arg JAR_FILE=petclinic/spring-petclinic-2.1.0.BUILD-SNAPSHOT.jar --build-arg JAVA_MODULES=petclinic/java_modules.txt -t openlibertyio/petclinic_java11_minijre_parms .

The name of the docker image (openlibertyio/petclinic_java11_minijre_parms here) is important since it will be used in the second phase. Of course, any name can be chosen as long as the same name is used in the FROM <base image name> statement in the docker file to build the final image. Note the two build-args being passed to the docker build command: JAR_FILE pointing to the Petclinic uber jar and JAVA_MODULES referring to the comma separated .txt file containing the list of the required Java modules as mentioned in Step 1.

Phase 2. Final image construction

The final image will be based on the rarely changed ‘base image’ created in Phase 1.


Split Spring Boot uber jar

We use the springBootUtility to create:

  • Thinned-out application jar, and
  • The dependent library jars as lib.index.cache
FROM open-liberty:kernel-java11 as staging


# Install unzip; needed later to unzip thinapp
USER root
RUN apt-get update \
    && apt-get install -y --no-install-recommends unzip
USER 1001

# Stage the fat JAR
COPY --chown=1001:0 ${JAR_FILE} /staging/myFatApp.jar
USER root
#### Thin the fat application; stage the thin app output and the library cache
RUN /opt/ol/wlp/bin/springBootUtility thin \
 --sourceAppPath=/staging/myFatApp.jar \
 --targetThinAppPath=/staging/myThinApp.jar \
USER 1001

# unzip thin app to avoid cache changes for new JAR
RUN mkdir /staging/myThinAppDir \
   && unzip -q /staging/myThinApp.jar -d /staging/myThinAppDir

Note that in this splitting process, the bundled middleware (Tomcat, Jetty, Undertow, or Liberty) gets discarded. We will execute the thinned application jar along with the dependent libraries in minimized Open Liberty; there is no need for the original embedded middleware.

To learn more about the springBootUtility, read the blog “Enterprise Spring Boot Deployments with Open Liberty and Kubernetes” (IBM Cloud blog, June 2019).


Create the final image

We start from the base image created earlier. For the defaultServer of the minimized Open Liberty of the base image, we copy the following at appropriate locations:

  • The lib.index.cache
  • The thinned application jar
  • server_myThinApp.xml
  • (Optional) The server script to set readonly for SCC for improving the startup time

Note that:

  • The config file server_myThinApp.xml refers to the myThinAppDir, created in Step 1 of this section: <springBootApplication location="myThinAppDir"/>
  • Opening a cache in read-only mode speeds up all operation to the cache but prevents the Java VM from making any updates to the cache.

FROM openlibertyio/petclinic_java11_minijre_parms

## Create the individual layers
COPY --chown=1001:0 --from=staging /staging/lib.index.cache /opt/ol/wlp/usr/shared/resources/lib.index.cache
COPY --chown=1001:0 --from=staging /staging/myThinAppDir /opt/ol/wlp/usr/servers/defaultServer/apps/myThinAppDir

# copy in the server.xml for myThinApp.jar
COPY --chown=1001:0 petclinic/server_myThinApp.xml /opt/ol/wlp/usr/servers/defaultServer/server.xml

#### copy in a server script which sets 'readonly' for server start
#### ....SERVER_IBM_JAVA_OPTIONS="-Xshareclasses:name=liberty-%u,nonfatal,cacheDir=\"${WLP_OUTPUT_DIR}/.classCache\",readonly ...
#### the SCC will only be read rather than populated at startup
COPY --chown=1001:0 petclinic/openliberty/server /opt/ol/wlp/bin

Note that the default server script used to start Open Liberty is modified to enable readonly SCC, as shown above.

At the end, we build the final container image:

docker build -f dockerfile_spring_petclinic_final_wscc --build-arg JAR_FILE=petclinic/spring-petclinic-2.1.0.BUILD-SNAPSHOT.jar -t openlibertyio/petclinic_java11_final_wscc .

Note that, for application code changes, one has to rebuild only the final container image as shown above, the base image remaining the same.

All the artifacts, including the docker files required to build the base and final container images, are available in the file


We did performance benchmark testing of the Petclinic applications measuring push sizes when container components change, along with the startup times, memory footprint, and throughput performance in the following representative environment:

  • LinTel: SLES 12.3, amd64-64-Bit, Intel(R) Xeon(R) Platinum 8180 CPU @ 2.50GHz, 64GB RAM.
  • Docker container: (--cpuset-cpus=0,1 --cpuset-mems=0).
  • Tomcat JDK used: OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.4+11) 2019-07-16.
  • Open Liberty JDK used: Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.15.1, JRE 11 Compressed Ref. 20190717_286 (JIT enabled, AOT enabled).

The benchmark testing did side-by-side comparison of the traditional Spring Boot Petclinic application with embedded Tomcat and the same Spring Boot Petclinic sample transformed to use minimized Open Liberty and minimized open OpenJDK-OpenJ9 JRE11 using the technique described earlier.

The benchmark focused on four attributes: push size, memory footprint, startup time, and throughput. We obtained the push sizes for container images using the docker history command on the container images. Refer to the Appendix for details.

The following table compares the push sizes for traditional Spring Boot Petclinic with that of the transformed one.

Push size for application change

Table 1. Push size comparisons

As you can see, for the original Spring Boot Petclinic image, any change in any component of the image pushes the entire 148.7MB image to the registry whereas in the transformed Petclinic the size of the image that gets pushed depends on the component(s) that get changed. In most cases, in an agile CI/CD environment the application jar will get changed most frequently for testing or deployment of bug fixes and enhancements. In such cases, less than 0.5MB of data will get pushed to the registry. In case of any change in Spring version with or without any application jar change, 41MB and not the entire 209MB will be pushed to the registry. Table 1 also details the push sizes if infrequently altered layers (SCC, middleware, JRE, Spring) change along with any upper layer.

Figures 1 and 2 schematically display the structure of the container images, the relevant layers, and the sizes that get pushed to the registry on application changes.

Original Spring Boot Petclinic uber jar

Figure 1. Original Spring Boot Petclinic uber jar

Modernized Spring Boot Petclinic jar

Figure 2. Modernized Spring Boot Petclinic jar

Figure 3 compares the memory footprint of the modernized Spring Boot Petclinic application to that of the original one. The modernization process has significantly lowered the memory requirement of the Spring Boot application by about 60%. The smaller footprint can be of great help in achieving higher packing density.

Memory footprint comparison

Figure 3. Memory footprint comparison

Figure 4 compares the startup time of the modernized Spring Boot Petclinic application and the original one. It is interesting to note that, presently, there is not much of a difference in startup times between the modernized and the original versions, even though the Open Liberty of the transformed Petclinic sample is an enterprise-ready JavaEE application server providing many extra capabilities around security, logging, monitoring etc. when compared to the Tomcat server of the original sample.

Startup time comparison

Figure 4. Startup time comparison

Figure 5 compares the throughput performance of the modernized Spring Boot Petclinic application to that of the original one. The modernization transformation boosted the throughput of the Petclinic Spring Boot application by 39% thereby significantly increasing its load handling capacity.

Throughput performance comparison

Figure 5. Throughput performance comparison

The throughput performance for each server was obtained by saturating the cpu of the docker containers. JMeter was used to generate load to each server driving the containers to near 100% cpu utilization. We used the jmeter jmx file of Petclinic to drive the load. The throughput performance scales linearly when additional cpus are provided to the cpuset of the docker containers. This clearly demonstrates that the throughput performance advantage of the modernized Spring Boot application remains steady even when the cpus of the docker containers are adjusted.

The use of SCC in the container image reduces the container startup time while it adds about 84.4MB to the total size of the modernized image – refer to the Tables 3A or 3B in the Appendix. It should be noted that, in general, the SCC layer does not get regenerated or pushed to the registry in case of application change. The SCC infrastructure detects when a class gets changed (i.e. a new class gets written) and avoids using the cached information for such a class to prevent the use of potential stale information. Typically, the application layer is quite thin in case of Spring Boot applications since they usually implement a simple microservice. In such cases, changing the application code and not being able to use the cached information for that code from the SCC is unlikely to affect startup time performance significantly. Changes to the Spring framework or the Open Liberty layer, which are expected to happen very infrequently, would have a bigger impact on the startup time since those layers contain a lot more classes. If those layers change, it is recommended to regenerate the SCC for continuing to experience good startup times.

By simply running the ‘docker build’ for the container base image, the SCC will be regenerated. The server start step of Phase 1, Step 3 will populate the SCC. It should be noted that for any changes in the Open Liberty or Java, one has to rebuild both the base as well as the final container images thereby automatically regenerating SCC. However, for any change in Spring framework, one can opt only to rebuild the final container image (Phase 2, Step 2), but that will not populate the SCC with new classes. For optimal startup speed, one may rebuild both the container images in case of changes in the Spring file too.

The comparison presented in this section clearly points out the superiority of the modernized and optimized version of the Spring Boot Petclinic application based on the Open Liberty application server when compared to the original one which uses Tomcat as the application server. Though containerized Spring Boot applications traditionally use the complete Java JRE and single layer container image construction technique (as mentioned in the Open Liberty blog “Optimizing Spring Boot apps for Docker“), we experimented with the Tomcat based Petclinic Spring Boot application by:

(a) First minimizing the OpenJDK Hotspot Java-JRE11 by using the jlink tool, and then
(b) Also applying the dual-layering technique to construct the final image

The overall push size for (a) gets reduced to 98MB, as shown in Figure 6.

Overall push size

Figure 6. Single-layer container image

The push size for application change for (b) becomes 44.7MB, as shown in Figure 7.

Push size for application change

Figure 7. Dual-layer container image

There are no changes in the memory footprint, the startup times, or the throughput performance for either of the above two improvements on the Tomcat-based Spring Boot Petclinic application; Figures 3, 4, and 5 remain valid. This result is interesting and may appear a bit counterintuitive — the throughput performance remains the same irrespective of the size of the underlying Java Runtime images: minimized or full.

It should be mentioned that in our benchmark study, the use of the jlink tool reduced the size of the Java Runtimes by about 50% to 80% — refer to Table 4 in the Appendix. The amount of actual reduction will of course vary depending on the application as well as on the original Java Runtime image size.

Now, clearly, Figure 7 represents the best possible case for the original Spring Boot Petclinic application which incorporated two vital steps of our modernization strategy — use of minimized JRE11 and dual-layer container image construction. It should be noted, that the modernized version of the Petclinic application based on Open Liberty prominently stands out even when compared to the best possible case of Tomcat based one corresponding to Figure 7. 0.5MB vs 44.7MB push size for application change, 60% lower footprint, similar startup times, and 39% better throughput performance. This clearly illustrates the superiority and practical usefulness of the present approach based on Open Liberty.

Google introduced the Jib plugin to containerize Java applications in an easy and efficient manner. Refer to the article “Introducing Jib — build Java Docker images better” for details. The Jib plugin uses a layered approach for building container images to reduce the ‘push size’ for changes in the application code. Jib’s container image construction approach is very similar to that of the technique presented in this article. The Jib plugin hides lot of complexities from users, thereby making it very easy for traditional Java developers to containerize their applications. However, Jib also takes away some flexibilities in precisely specifying the components intended to construct container images. For example, there is no way one can specify the exact Java modules needed for specific applications for incorporating a minimized JRE in the generated container image.

We compared performance between:

  • the Spring Boot Petclinic application containerized by Jib’s plugin, which uses Tomcat embedded in the Spring Boot application along with Hotspot JVM, and
  • the transformed Petclinic application, which uses Open Liberty with OpenJ9 JVM.

The comparison shows that:

(a) There are no significant differences in ‘push sizes’ for application changes or in startup times between these two versions of Petclinic. The Jib plugin reduces push sizes in traditional Tomcat-based Spring Boot applications.
(b) The memory footprint of Petclinic transformed using Open Liberty and OpenJ9 is about 50% smaller than that of the Jib-created Petclinic.
(c) The throughput performance of Petclinic transformed using Open Liberty and OpenJ9 is about 55% more than that of the Jib-created Petclinic.

These results again establish the superiority of the modernization strategy for the Spring Boot applications using Open Liberty and OpenJ9 JVM, as detailed in this article.


This article has described a simple technique for transforming any Spring Boot application to its modernized and optimized version for a CaaS environment. On an application change, unlike the original Spring Boot application, the modernized version pushes a very small amount of data to the container image registry thereby massively facilitating the use of CI/CD process where typically the application changes are frequent. The significantly smaller footprint of the modernized application also contributes towards easier and economical scaling. The startup time of the transformed modernized version essentially remains the same as that of the original version. The modernized version’s use of Open Liberty has the added benefit of superior throughput performance implying much better load handling over the original version using Tomcat.

The technique revolves around two docker files. The docker files minimize Open Liberty, OpenJDK-OpenJ9 JRE11 and also extract the application jar from a traditional Spring Boot application. The creation of the docker files can be a bit involved process. However, creation of docker files is a one-time activity, they do not have to be created from scratch for the same application when it goes through major enhancements or even for other Spring Boot applications — new files can be based of the existing ones. Moreover, we parameterized the docker files, which accepts two parameters: (a) the name and location of the traditional to be transformed Spring Boot jar, and (b) the list of the Java modules needed to run the application. Once these parameters are passed to the docker files the modernized container image is simple to create. One can easily generalize the two docker file templates included in this article a bit to modernize any Spring Boot application by simply passing in appropriate values of the above mentioned two parameters.

The modernized Spring Boot container image can be deployed in an enterprise using the already existing CI/CD pipeline or simply using the standard CaaS commands like kubectl or through appropriate GUIs. The use of the modernized versions of Spring Boot applications will significantly increase the efficiency of the existing CI/CD pipelines by substantially reducing the push sizes.

Though we have used Open Liberty as the middleware, the present technique is equally applicable if WebSphere Liberty is used. From our benchmark study, the use of WebSphere Liberty increases the size of the middleware from 24.9 MB (see Tables 3A or 3B in the Appendix) to about 27.7MB with no other perceptible differences in memory footprint, startup time, or throughput performance of the containers.

We used Eclipse OpenJ9 for creating our optimized Spring Boot application. Eclipse OpenJ9 is very suitable for applications hosted in cloud. When using OpenJ9 for applications, with a little configuration, one can almost double the startup speed and halve the memory footprint, when compared to other Java VM implementations (see OpenJ9 performance data for more details). With market increasingly favoring cloud for application development and deployment, some may consider OpenJ9 to be the natural choice as Java virtual machine for applications. It should be noted that OpenJ9 continues to evolve by adopting and incorporating innovations specifically aimed at running Java efficiently on cloud, e.g., SCC improvement for working better inside containers, new exciting strategies such as JIT server.


Eric Herness, IBM Fellow; Alan Little, DE; Ian Robinson, DE; Keith Whitehead, BUE; Melisa Modjeski, Director; Garth Tschetter, Director; and Erin Schnabel, STSM reviewed early drafts and provided helpful suggestions for technical improvements. William Kornado and Sarah Domina helped in improving the presentation quality.

A big ‘thank you’ to Chris Soltow, Solutions Architect, Caleres for drawing our attention to some of the issues that one can potentially face in agile CaaS environments.


The appendix contains the outputs of the docker history command when executed on the container images of the original and modernized Spring Boot applications along with the sizes of the original and minimized forms of the Java Runtimes used in our benchmark tests. The output of the command shows different layers of the image along with their creation timestamps, creating commands, sizes, and comments if any. By comparing the creation time stamps of the individual layers before and after application (or other entities like Spring, JRE, etc.) change from the outputs of the docker history commands, we can determine the push size — the exact layers and the actual amount of bytes that will be pushed to the registry. Note that only the freshly created layers will be pushed to the registry on a docker push command when a container image gets rebuilt.

In all the following tables, the first four columns are from the docker history output while the last column we manually added to point out to the contents of the layer.

Push size for application change

Table 2A. docker history: Original Petclinic container before application change

Push size for application change

Table 2B. docker history: Original Petclinic container after application change

Tables 2A and 2B show the docker histories of the original Petclinic uber jar container before and after a change in the application jar. Note from the creation timestamps of the layers, that with the exception of the OS layer (RHEL 7.6 UBI) all the other docker layers (shaded in the Table 2B) get rebuilt even on an application jar change. All these changed layers will be pushed to the registry on a docker push operation. From Table 2B, we see that 108.7MB (44.7MB application jar + 104MB JRE) is the ‘push size’ for the original Petclinic Spring Boot application even when only the application changes.

Push size for application change

Table 3A. docker history: Modernized Petclinic container before application change

Push size for application change

Table 3B. docker history: Modernized Petclinic container after application change

Tables 3A and 3B correspond to the docker histories of the modernized Petclinic Spring Boot container built from the two docker files before and after application change. Again, comparing the creation timestamps of the individual layers before and after application (or other entities) change, we can determine the push size. Table 3B clearly indicates that about 425KB is the ‘push size’ for the modernized Petclinic Spring Boot application on an application change.

Push size for application change

Table 4. Java Runtime sizes

Table 4 shows the sizes of the two versions of the Java Runtimes we used in our transformation technique and benchmark tests along with the sizes of their minimized versions produced by the jlink tool for the Petclinic application.