Vulnerability-Free Java Containers: A Practical Guide
In today’s cloud-native landscape, securing Java applications isn’t just about the code we write — it’s about 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, starting from the Java JRE base container image, up through our Java application dependencies, and the application code itself.
Last year, I was working with a client that 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 conducted a survey of popular Java container base 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 February 7, 2025:
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 to use, but other tags for specific Java versions require a Chainguard subscription. Yet, if you are able to run the latest version of the Java JRE (currently 23), this image is a great option.
If you want to target a specific version of Java and Chainguard isn’t used by your organization, 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 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
Let’s walk through building a Java application container with zero vulnerabilities using Chainguard’s JRE base image and Google’s Jib Maven plugin. 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:
<from>
<image>chainguard/jre:latest</image>
…
</from>
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 are 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 applications 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:
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
</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 that has 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 secure and stay secure. Stay informed about new vulnerabilities, keep your dependencies updated, and regularly review your security posture to maintain a strong security stance for your Java applications.
Finally, don’t forget to check out my demo repo where you can experiment with everything discussed in this blog.