This is the final article in a series where we are updating a monolithic Java EE application to function as a microservice and run in a distributed cloud environment such as Red Hat OpenShift. In the first article, we set up the legacy Java application and defined our goals. Then, we upgraded the Java environment to Jakarta EE. In the last article, we used MicroProfile to prepare the application for use in a distributed environment.
Read the whole series:
- 
	
Part 1: An incremental approach using Jakarta EE and MicroProfile
 - 
	
Part 3: Integrate MicroProfile services
 - 
	
Part 4: Optimize the runtime environment
 
We now have all the functionality we planned to add to our cloud-ready Java application. However, the resulting image is substantially larger than our initial image. This is not optimal because we'll need to transfer the image over the network and run it on a platform-as-a-service (PaaS) in the cloud. Resources such as memory, CPU, and RAM use factor into the costs charged by a PaaS provider. In this final article, we'll optimize the runtime to reduce the image's size and memory footprint. The benefits of optimizing the runtime include:
- Better cloud-resource utilization.
 - Decreasing startup and scale-up time.
 - Minimizing the attack surface.
 
Runtime optimization with JBoss EAP, JBoss EAP XP, and Galleon
We'll use Red Hat JBoss Enterprise Application Platform (JBoss EAP) and JBoss EAP XP to decrease the size of our application image while also increasing container security. First, we'll develop a runtime image that eliminates development tools (such as Maven artifacts) that were present in the original Source-to-Image (S2I) environment. Then, we'll use Galleon to trim the application features and provide customization for JBoss EAP and its image’s footprint.
Note that we'll use the same GitHub repository we've used for the previous articles in the series. To start, switch to the git tag that contains the source code used to implement the Galleon version:
$ git checkout tags/Galleon_Runtime_version
Now, delete the previous version of the application to start with a clean environment:
$ oc delete all --selector app=weather-app-eap-cloud-ready 
$ oc delete is weather-app-eap-cloud-ready
$ oc delete bc weather-app-eap-cloud-ready
Import the image for the JBoss EAP XP 2.0 OpenJDK 11 runtime:
$ oc import-image jboss-eap-7/eap-xp2-openjdk11-runtime-openshift-rhel8 --from=registry.redhat.io/jboss-eap-7/eap-xp2-openjdk11-runtime-openshift-rhel8 --confirm
Update the buildConfig
Now let's focus on the buildConfig.yaml file under the k8s directory. In that file, I defined a chained build with two buildConfig objects: weather-app-eap-cloud-ready-build-artifacts and weather-app-eap-cloud-ready. The first one is the S2I builder image that contains a complete JBoss EAP server with tooling needed during the S2I build. The second one has the runtime image that contains dependencies needed to run JBoss EAP. The first build creates the JBoss EAP XP instance and the application to be deployed, whereas the second build excludes the development tools not needed in the production environment. Figure 1 summarizes the components of the development process and their relationships.

Here is a snapshot of the chained build:
kind: ImageStream
apiVersion: image.openshift.io/v1
metadata:
  name: weather-app-eap-cloud-ready-build-artifacts
  labels:
    application: weather-app-eap-cloud-ready-build-artifacts
---
kind: ImageStream
apiVersion: image.openshift.io/v1
metadata:
  name: weather-app-eap-cloud-ready
  labels:
    application: weather-app-eap-cloud-ready
---
kind: BuildConfig
apiVersion: build.openshift.io/v1
metadata:
  name: weather-app-eap-cloud-ready-build-artifacts
  namespace: redhat-jboss-eap-cloud-ready-demo
  labels:
    build: weather-app-eap-cloud-ready-build-artifacts
spec:
  output:
    to:
      kind: ImageStreamTag
      name: 'weather-app-eap-cloud-ready-build-artifacts:latest'
  resources: {}
  strategy:
    type: Source
    sourceStrategy:
      from:
        kind: ImageStreamTag
        namespace: redhat-jboss-eap-cloud-ready-demo
        name: 'eap-xp2-openjdk11-openshift-rhel8:latest'
  source:
    type: Binary
    binary: {}
---
kind: BuildConfig
apiVersion: build.openshift.io/v1
metadata:
  labels:
    application: weather-app-eap-cloud-ready
  name: weather-app-eap-cloud-ready
spec:
  output:
    to:
      kind: ImageStreamTag
      name: weather-app-eap-cloud-ready:latest
  source:
    dockerfile: |-
      FROM eap-xp2-openjdk11-runtime-openshift-rhel8
      COPY /server $JBOSS_HOME
      USER root
      RUN chown -R jboss:root $JBOSS_HOME && chmod -R ug+rwX $JBOSS_HOME
      USER jboss
      CMD $JBOSS_HOME/bin/openshift-launch.sh
    images:
    - from:
        kind: ImageStreamTag
        name: weather-app-eap-cloud-ready-build-artifacts:latest
      paths:
      - sourcePath: "/s2i-output/server/"
        destinationDir: "."
  strategy:
    dockerStrategy:
      imageOptimizationPolicy: SkipLayers
      from:
        kind: ImageStreamTag
        name: eap-xp2-openjdk11-runtime-openshift-rhel8:latest
        namespace: redhat-jboss-eap-cloud-ready-demo
    type: Docker
  triggers:
  - imageChange: {}
    type: ImageChange
Configure JBoss EAP XP using Galleon
We'll also need to configure JBoss EAP XP to run it using the runtime image. To configure this mode, I set the following environment variables in the environment file under the .s2i directory:
#GALLEON_PROVISION_DEFAULT_FAT_SERVER=true
GALLEON_PROVISION_LAYERS=jaxrs-server,microprofile-platform
S2I_COPY_SERVER=true
The final image will contain a server, a packaged application, and the runtime required to launch JBoss EAP. As shown in the YAML snippet, there are two properties related to the Galleon framework. The first one creates a full-featured JBoss EAP XP subsystem:
GALLEON_PROVISION_DEFAULT_FAT_SERVER=true
But our target is not only to have a slim and more secure container image that omits unnecessary tools. We also want to improve the use of cloud resources by removing unused subsystems from JBoss EAP XP. For this reason, I commented out the GALLEON_PROVISION_DEFAULT_FAT_SERVER property. To include only the necessary subsystems, I also set the GALLEON_PROVISION_LAYERS property with the names of the subsystems needed to run my application. The jaxrs-server subsystem provides support for JAX-RS and JPA, while the microprofile-platform subsystem includes the MicroProfile capabilities we added in Part 3.
I also set the property S2I_COPY_SERVER to copy the result of the first build, named weather-app-eap-cloud-ready-build-artifacts in the buildConfig.yaml, into the final runtime image as described in the weather-app-eap-cloud-ready build, which is always set in the buildConfig.yaml file. Without this property, you can't complete this step.
Create the new runtime image
Now it’s time to create the ImageStreams and the chained buildConfig to make the runtime image with JBoss EAP XP 2 and the application:
$ oc create -f k8s/buildConfig.yaml
Then, start the build of the application on OpenShift:
$ oc start-build weather-app-eap-cloud-ready-build-artifacts --from-dir=. --wait
I suggest that you check when the second build finishes with this command:
$ oc get build weather-app-eap-cloud-ready-1 --watch
After the status moves from Pending to Complete, you can create the weather application for JBoss EAP XP 2 and configure it:
$ oc create -f k8s/weather-app-eap-cloud-ready.yaml
You can then test your application, using the steps I have described in the previous articles, to verify that it is still working.
Reviewing the outcomes
Now it’s time to check the return on investment for the operations we've just performed. Figure 2 shows the new container image size.

Figure 3 shows the new memory footprint.

Consider these outcomes:
- Container image size: The previous application image, with Jakarta EE and MicroProfile features plus all of JBoss EAP XP and RHEL 8 UBI, takes up 455 MB. The final image, obtained through the optimizations we carried out in this article, is 294 MB, a savings of 35%.
 - Memory footprint: The previous application release, with Jakarta EE and MicroProfile features, plus all of JBoss EAP XP and RHEL 8 UBI, requires 1,000 MB of memory. The final release, obtained through optimization, requires 718 MB of memory, a savings of 28%.
 
Conclusion to Part 4
This series has gone through the steps to modernize a legacy Java EE application using Jakarta EE and Eclipse MicroProfile. The resulting final application includes features and services that are beneficial for microservice applications running in the cloud. By repeating the processes shown in the series, you can break your monolithic Java applications into small and independent modules without needing to heavily change your source code. The resulting runtime environment is:
- Optimized for the cloud and containers
 - Lightweight, with a flexible architecture
 - More productive for developers
 - Flexible in management, configuration, and administration
 - Oriented to supporting and standardizing microservices development
 - Based entirely on open source tools and standards
 
Don’t stop evolving all of your applications! Continuous improvement is the key to the success of your architecture.
More about modernization: Application modernization patterns with Apache Kafka, Debezium, and Kubernetes.
Last updated: January 12, 2024