The deployment of Java applications within containerized environments has undergone a significant evolution, moving from monolithic virtual machines to agile, OCI-compliant containers. At the center of this transition for legacy and modern enterprise systems is OpenJDK 11, a Long-Term Support (LTS) release that provides the stability required for mission-critical software. Utilizing Docker to encapsulate OpenJDK 11 allows developers to abstract the Java Runtime Environment (JRE) and Java Development Kit (JDK) from the underlying host operating system, ensuring that the "write once, run anywhere" philosophy of Java is extended to the infrastructure layer. This technical deep dive explores the various distributions of OpenJDK 11 images, including those from AdoptOpenJDK and Red Hat, the intricacies of container-aware JVM behavior, and the operational strategies for building and deploying Java applications using these images.
The Ecosystem of OpenJDK 11 Docker Images
The availability of OpenJDK 11 in the Docker ecosystem is not monolithic; rather, it is provided by various vendors and community projects, each offering different base operating systems and optimization profiles.
AdoptOpenJDK Distributions
AdoptOpenJDK provides a comprehensive set of Docker images built from a fully open-source set of build scripts and infrastructure. These images are designed to be vendor-neutral and are rebuilt daily to ensure that the underlying operating system patches are current.
The AdoptOpenJDK images are split across different DockerHub repositories to support a wide variety of operational requirements. These images utilize the same underlying Java binaries but vary based on the OS flavor they are packaged with.
The following table details the available build flavors and their specific characteristics:
| OS Flavor | Image Variant | Target Architecture | Description |
|---|---|---|---|
| CentOS | centos-slim | x86_64, aarch64, armv7l, ppc64le | Lightweight CentOS base with JDK 11 |
| CentOS | centos-jre | x86_64, aarch64, armv7l, ppc64le | Full CentOS base with JRE 11 |
| Debian | debian | x86_64, aarch64, armv7l, ppc64le, s390x | Full Debian base with JDK 11 |
| Debian | debian-slim | x86_64, aarch64, armv7l, ppc64le, s390x | Minimal Debian base with JDK 11 |
| Debian | debian-jre | x86_64, aarch64, armv7l, ppc64le, s390x | Full Debian base with JRE 11 |
| Debian | debianslim | x86_64, aarch64, armv7l, ppc64le, s390x | Optimized minimal Debian JRE/JDK |
The technical implementation of these images ensures multi-arch support. This means a single command set can be used across different CPU architectures, including x86_64 and ARM, without needing to modify the Dockerfile logic.
Red Hat OpenJDK and UBI Integration
Red Hat provides a specialized build of OpenJDK 11, which is a free and open-source implementation of the Java Platform, Standard Edition (Java SE). This version is specifically engineered as a Kubernetes-native Java runtime, optimized for containerized and serverless workloads.
The Red Hat implementation is built upon Red Hat Universal Base Images (UBI). UBI images are OCI-compliant container base operating system images that are freely redistributable. This provides a secure, enterprise-grade foundation for the Java runtime.
The Red Hat OpenJDK 11 image (specifically ubi8/openjdk-11) includes several technical specifications:
- Provider: Red Hat
- Maintainer: Red Hat OpenJDK (
[email protected]) - Image Version: 1.21
- Architecture: amd64
- Working Directory:
/home/jboss - User ID: 185
- Exposed Ports:
8080/tcp,8443/tcp,8778/tcp
A critical feature of the Red Hat image is the inclusion of Source-to-Image (S2I) integration scripts. S2I is a specialized tool for Red Hat OpenShift that allows the platform to take raw source code and automatically build a production-ready container image without requiring the developer to write a complex Dockerfile. This streamlines the deployment pipeline by automating the transition from code to container.
Technical Implementation and Deployment Patterns
Deploying a Java application using OpenJDK 11 requires a strategic approach to the Dockerfile to ensure image size is minimized and security is maximized.
Running Pre-built JAR Files
When an application is already packaged as a "fat-jar" (a JAR containing all dependencies), the Dockerfile should be kept minimal. Using the AdoptOpenJDK UBI image, the process involves creating a dedicated application directory and executing the JAR.
The following configuration demonstrates the professional standard for deploying a pre-built JAR:
dockerfile
FROM adoptopenjdk/openjdk11:ubi
RUN mkdir /opt/app
COPY japp.jar /opt/app
CMD ["java", "-jar", "/opt/app/japp.jar"]
To instantiate this environment, the following terminal commands are utilized:
bash
docker build -t japp .
docker run -it --rm japp
For those requiring specific build versions to ensure consistency across environments (immutability), the build number must be appended to the tag. For example:
bash
docker run --rm -it adoptopenjdk/openjdk11:jdk-11.0.9.1_1 java -version
This command will output the specific version details:
openjdk version "11.0.9.1" 2020-11-04
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.9.1+1)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.9.1+1, mixed mode)
Compiling Within Containers
In some development workflows, it is necessary to compile the Java code within the container itself. This is common in CI/CD pipelines where the build environment must be identical to the production environment.
The standard pattern for compiling and running a project is as follows:
dockerfile
FROM openjdk:11
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN javac Main.java
CMD ["java", "Main"]
The image is then built and executed:
bash
docker build -t my-java-app .
docker run -it --rm --name my-running-app my-java-app
Using Docker as a Compiler (Ephemeral Volumes)
There are scenarios where running the application inside a container is inappropriate, but using the container's JDK for compilation is desired to avoid installing Java on the host machine. This is achieved by mounting the host's current working directory as a volume.
The command to achieve this is:
bash
docker run --rm -v "$PWD":/usr/src/myapp -w /usr/src/myapp openjdk:11 javac Main.java
In this workflow, the -v "$PWD":/usr/src/myapp flag maps the local directory to the container, and the -w flag sets the working directory. The javac command compiles Main.java and outputs the Main.class file directly back to the host's filesystem.
JVM Container Awareness and Resource Management
One of the most significant technical challenges in containerizing Java is the interaction between the Java Virtual Machine (JVM) and the container's resource limits.
The CPU and RAM Detection Problem
Historically, the JVM attempted to detect the available hardware resources (CPU cores and RAM) to configure internal parameters, such as the number of Garbage Collection (GC) threads to spawn. However, older versions of the JVM used system APIs that returned host-wide values rather than the limits imposed by the Docker container (Cgroups).
The real-world consequence of this discrepancy is catastrophic: if a container is limited to 2GB of RAM but the host has 64GB, the JVM may attempt to allocate a heap size based on the 64GB, leading to an Out-of-Memory (OOM) kill by the Docker daemon. Similarly, spawning GC threads based on the host's 32 cores when the container is limited to 2 cores causes excessive CPU contention and performance degradation.
The OpenJDK 11 Solution
Within Linux containers, OpenJDK versions 8 and later (including OpenJDK 11) have been updated to correctly detect container-limited resources. This means the JVM now recognizes the Cgroup limits set by Docker or Kubernetes. This ensures that the JVM's internal ergonomics—such as heap size calculation and thread pool sizing—are aligned with the actual resources allocated to the container, preventing stability issues and optimizing performance in cloud-native environments.
Lifecycle, Licensing, and Maintenance
The maintenance of OpenJDK images is a shared responsibility between the providers and the users.
The Shift to Eclipse Temurin
A significant shift occurred in the community regarding the production of OpenJDK builds. Red Hat previously produced "OpenJDK Project Builds" for versions 8u and 11u as a service to the community for x86_64, aarch64, and Windows. However, these builds were retired after the July 2022 CPU update (specifically versions 11.0.16 and 8u342).
The industry has shifted toward Eclipse Adoptium (formerly AdoptOpenJDK), which produces Eclipse Temurin builds. These are JCK (Java Compatibility Kit) tested, ensuring they meet the official Java SE specification. Consequently, the community is encouraged to transition from the legacy openjdk tags to Temurin-based images to ensure they receive the latest security fixes.
Licensing and Legal Compliance
The legal framework surrounding OpenJDK 11 images is multifaceted:
- Dockerfiles and associated scripts: These are licensed under the Apache License, Version 2.0.
- Java and Trademarks: Java and all associated trademarks and logos are the property of Oracle and/or its affiliates.
- Base Distribution Software: Since these images are built on Linux distributions (CentOS, Debian, UBI), other software such as Bash is included. These are subject to their own respective licenses.
It is the absolute responsibility of the image user to ensure that the use of these images complies with the licenses of all software contained within the distribution.
Conclusion
The utilization of OpenJDK 11 within Docker provides a robust foundation for enterprise Java applications, balancing the need for stability with the flexibility of containerization. Whether opting for the vendor-neutral AdoptOpenJDK images, the enterprise-hardened Red Hat UBI images, or the JCK-tested Eclipse Temurin builds, the key to success lies in understanding the underlying OS flavor and the JVM's interaction with container resources. The transition toward OCI-compliant images and the ability of the JVM to recognize container limits have eliminated the "noisy neighbor" and OOM issues that plagued earlier Java containerization efforts. For modern architects, the choice of image—whether it be a "slim" version for reduced attack surface or a full JDK for build-time compilation—must be driven by the specific requirements of the deployment pipeline and the target architecture.