Home / Technology / Vulnerability-Free Java Containers: A Practical Guide

Vulnerability-Free Java Containers: A Practical Guide

In today’s cloud native landscape, securing Java applications isn’t just about the code we write but the entire container stack. While Java has maintained a strong security record, incidents like Log4Shell have shown us that vigilance is crucial. We need a comprehensive approach to preventing vulnerabilities, from the Java JRE base container image to our Java application dependencies and the application code itself.

Last year, I worked with a client who complained about vulnerabilities in their container base image for Node.js. That triggered me to do more research on base images, including Java.

The State of Java Container Images

I surveyed popular Java container-based images using the open source grype vulnerability scanner. The results were eye-opening:

  • Almost all base images, including those from major providers, contained multiple vulnerabilities
  • Vulnerability counts ranged from just a few to hundreds!
  • Only one base image had zero vulnerabilities: Chainguard’s JRE base image

Here are the survey results as of Feb. 7, 2025:

JRE Image Vulnerabilities Image Size (Uncompressed)
mcr.microsoft.com/openjdk/jdk-21-distroless:latest 5 Critical
13 High
8 Medium
0 Low
331MB
bitnami/java:latest 3 Critical
139 High
363 Medium
17 Low
629MB
openjdk:23-slim (Deprecated) 3 Critical
3 High
27 Medium
9 Low
468MB
gcr.microsoft.com/openjdk/java21-debian12:latest 1 Critical
2 High
1 Medium
0 Low
202MB
openjdk:latest (Deprecated) 0 Critical
24 High
56 Medium
33 Low
487MB
ibm-semeru-runtimes:open-23-jre 25 Medium
34 Low
282MB
eclipse-temurin:latest 43 Medium
37 Low
478MB
eclipse-temurin:23-jre-alpine 7 Medium
11 Low
205MB
container-registry.oracle.com/java/openjdk:latest 5 Medium 686MB
bellsoft/liberica-openjre-alpine:latest 2 Medium 149MB
amazoncorretto:latest 2 Medium 366MB
amazoncorretto:23-alpine 2 Medium 366MB
chainguard/jre:latest 0 226MB

Given that the Chainguard Java JRE image is the only one without vulnerabilities, you may be compelled to try it out. The latest tag is free, but other tags for specific Java versions require a Chainguard subscription. Yet, this image is an excellent option if you can run the newest version of the Java JRE (currently 23).

If you want to target a specific version of Java and your organization doesn’t use Chainguard, my recommendation would be Amazon Corretto. I have been using Corretto for over two years with no issues, and their images are solid with a low number of vulnerabilities. Corretto can be swapped in as the base image the same way as Chainguard’s, as shown in this guide.

Java Container Build Options

To package a container image for your Java application, there are a few options, which include:

  • Traditional Dockerfile: While straightforward, this approach often results in inefficient image layering and requires a Docker (or equivalent) daemon for builds.
  • Cloud Native Buildpacks: Buildpack integration offers a convenient path but cannot easily customize the base image.
  • Google’s Jib: A streamlined plugin that offers daemon-less builds, efficient layering, and base image flexibility.

For my Java (Spring Boot) demo application, which is provided on GitHub, Jib is the preferred container build approach, but a Dockerfile is also provided as an alternative. If you want to understand how Jib’s image layering works, please see the README of my demo application for how to use dive to examine the layers of your container image.

Building a Zero-Vulnerability Container Image

Using Chainguard’s JRE base image and Google’s Jib Maven plugin, let’s build a Java application container with zero vulnerabilities. If you use Gradle, the Jib Gradle plugin will work similarly to the Maven plugin.

1. Configure Jib in Your Maven Project

To change the base image to the Chainguard JRE image, add the following Jib Maven plugin configuration to your pom.xml as done in the demo application:

    chainguard/jre:latest
    …

A sample Dockerfile using Chainguard’s JRE base image is provided if you prefer not to use Jib.

2. Build Your Container Image

Run the following Maven Jib command to build a local container image for the demo application (requires Docker):

./mvnw jib:dockerBuild

If using the Dockerfile instead of Jib, see the demo application’s README for further instructions on how to build the container image using docker build.

3. Verify Zero Vulnerabilities

Now, with the image built, you can use grype to scan your newly built image that resides on your local machine:

grype docker.io/your-id/your-image:latest

With the latest Chainguard base image and all of the Maven dependencies updated, you should see the following result: No vulnerabilities found

✔ Loaded image                                                                                                                                 docker.io/your-id/your-image:latest
 ✔ Parsed image                                                                                                                            sha256:18f64293e66f391c41e7363c989687b852203e0fb9c32744f0c5d7b80a0e7f79
 ✔ Cataloged contents                                                                                                                             0b944452baefa268f36e01336d79d0fb1fc35f13bef0c11c262d3ef2c9e46d06
   ├── ✔ Packages                        [74 packages]  
   ├── ✔ File digests                    [1,186 files]  
   ├── ✔ File metadata                   [1,186 locations]  
   └── ✔ Executables                     [121 executables]  
 ✔ Scanned for vulnerabilities     [0 vulnerability matches]  
   ├── by severity: 0 critical, 0 high, 0 medium, 0 low, 0 negligible
   └── by status:   0 fixed, 0 not-fixed, 0 ignored 
No vulnerabilities found

Proactive Vulnerability Detection With SBOMs

Rather than waiting until after container build time, you can catch vulnerabilities earlier in your development cycle by generating and scanning a Software Bill of Materials (SBOM). This SBOM captures all of your application’s dependencies in a standardized JSON format that can be read by commonly used scanning tools like grype.

CycloneDX offers a Maven plugin (or a Gradle plugin) for building an SBOM based on your application’s dependency tree. Add the following plugin to your pom.xml:

    org.cyclonedx
    cyclonedx-maven-plugin

Now you can scan your application dependencies before containerization using grype:

grype target/classes/META-INF/sbom/application.cdx.json
 ✔ Scanned for vulnerabilities     [4 vulnerability matches]  
   ├── by severity: 0 critical, 2 high, 1 medium, 1 low, 0 negligible
   └── by status:   4 fixed, 0 not-fixed, 0 ignored 
NAME               INSTALLED  FIXED-IN  TYPE          VULNERABILITY        SEVERITY 
logback-core       1.5.12     1.5.13    java-archive  GHSA-pr98-23f8-jwxv  Medium    
logback-core       1.5.12     1.5.13    java-archive  GHSA-6v67-2wr5-gvf4  Low       
tomcat-embed-core  10.1.33    10.1.34   java-archive  GHSA-5j33-cvvr-w245  High      
tomcat-embed-core  10.1.33    10.1.34   java-archive  GHSA-27hp-xhwr-wr2m  High

As you can see in this case, the SBOM and grype discovered an outdated Tomcat version in Spring Boot 3.4.0 with two High vulnerabilities. The cool thing about the SBOM approach is that you can standardize on a single vulnerability scanning tool like grype for both SBOM and images. Typically, for Java, vulnerability-checking tools are separate from container image scanning tools, so consolidation to a single vulnerability scanning tool may be beneficial.

Conclusion

While Chainguard currently offers the only zero-vulnerability Java base image, other image providers are making progress on reducing the number of vulnerabilities. Yet, a comprehensive security strategy is still necessary, which includes:

  • Careful base image selection
  • Regular vulnerability scanning container images
  • SBOM generation and scanning
  • Automated dependency update processes (i.e., Dependabot)

By following these practices and using modern tools, you can build and maintain containerized Java applications that start and stay secure. Stay informed about new vulnerabilities, update your dependencies, and regularly review your security posture to maintain a strong security stance for your Java applications.

The post Vulnerability-Free Java Containers: A Practical Guide appeared first on The New Stack.