Paketo Buildpacks: ready for Java 26 & more!

Paketo Buildpacks, the default way to create container images with Spring Boot, will include support for Java 26 the same week it will be released in major OpenJDK distributions (Oracle, BellSoft Liberica, Azul Zulu, etc.)
Let’s take this as an opportunity to (re)discover what Paketo Buildpacks are and what they allow you to do!

Paketo Buildpacks: Not the Only Buildpacks Around!

A World of Buildpacks

My first memory of using buildpacks dates back to when I worked for a bank around 2020 (yes, that year!) and my team created and maintained a bunch of shell scripts that would copy a templated Dockerfile, add the public certificates from the corporate Certification Authority (CA), and then build the Dockerfile into an image (and even create a Helm chart!); but we soon learned that there were open-source buildpacks that supported most of our use cases!

To be fair, I had already been using buildpacks when I tested Heroku in 2012, but I just did not know it: they were hidden by the platform (well, if you pay attention to the logs, you can see them in action). Then a few years later I heard (during a KubeCon) about Heroku engineers (now Salesforce engineers) talking about a new open specification to define the lifecycle and tooling around buildpacks to build OCI (Docker) images.

And finally, when I landed at VMware at the beginning of 2023, I joined a team of Cloud Foundry (yes, the self-hosted PaaS running on VMs built with BOSH and its Java Buildpack written in … Ruby!) and Paketo buildpacks maintainers and their closed-source Tanzu equivalents.

Everything then clicked in my head: the CNCF buildpack (Cloud Native Buildpacks, CNBs) project is about the specification, lifecycle and the pack CLI; while Paketo Buildpacks are an implementation (Heroku and Google Cloud maintain different implementations) of this specification (detect and then build) for many programming languages: Go, PHP, .NET, Apache HTTPd, many others, and of course Java.

Cloud Foundry (sometimes referred to as v2) and Paketo Buildpacks (sometimes referred to as v3) are very different though: if we take the example of the Java buildpack:

  • in Cloud Foundry it is only made of a single Ruby-based buildpack for all use cases (Spring Boot customization, APM integration injecting agents, etc.) and it never builds the jar/war nor does it create an OCI image but rather a process that runs on a Diego Cell (an isolated container running on a VM) and is exposed to a gateway
  • in Paketo Buildpacks, it is a collection (often called a composite buildpack) of single purpose Go-written buildpacks (a buildpack to get a JDK, another to use maven, gradle, APM integrations, etc.) that eventually creates an OCI image

I also need to mention another family of buildpacks: the Nixpacks – well I recently discovered they’re being deprecated and replaced with railpacks – they use BuildKit under the hood and support Java (but they do not support as many integrations as Paketo)

Where and How Are Buildpacks Used?

Buildpacks (any kind of buildpacks actually) have always been used in a context of Platform as a Service (PaaS) – Heroku, Cloud Foundry, Tanzu as I laid out previously.

But you can see usage of buildpacks in other PaaS such as:

  • Railway – they use Nixpacks
  • Paketo Buildpacks are used (or at least are supported via CNBs specification) almost everywhere else!
    • Fly.io, Porter
    • AWS App Runner, Google Cloud, Microsoft Azure Container Apps, IBM Cloud Code Engine, DigitalOcean App Platform
    • Epinio, the self-hosted Kubernetes PaaS

It makes sense: with all those platforms, the promise is to let the developer care about their code, not about the deployment; the platform will use the buildpacks to detect what is needed, and how to build and bring the required components

Let’s not forget the end users (application architecture teams usually) who roll out their own internal PaaS! I have seen many engineers working for big organizations like banks or insurance companies use Paketo Buildpacks to make their build and deployment standard across teams and programming languages.

“But I’m Fine with the Good Old Dockerfile” You Say

I understand that statement! The Dockerfile is well understood today, so why would you go and learn another higher-level construct?

In the last chapter, I already started hinting at some advantages of the buildpacks over the Dockerfile in a big organization context:

  • with Paketo Buildpacks, you can provide all your teams with an internal PaaS (integrate them to your Continuous Integration and when they release a tag the teams get their OCI image published) – no more time spent writing or maintaining a Dockerfile
  • with Paketo Buildpacks, you control how images are built: no more proliferation of Dockerfiles across projects: every team will build the same way, and you can even make sure some concerns like CA public certificates, APM agents (Datadog, AppDynamics) and SBOMs are integrated exactly how you want for the whole organization
  • with Paketo Buildpacks you have the best support available for Spring Boot app deployment: including integration with Kubernetes Service Binding, and performance customization (CDS, AOT Cache, AOT, unpacked format – more on that in the Spring Boot specific chapter below) – without trying to figure out how a training run is supposed to happen!

Buildpacks streamline the inclusion of an additionnal CA certificate

Maintaining a Dockerfile means making sure the base OS, its dependencies, and the JVM are on the latest supported version, to avoid building with a CVE-ridden base.

So you’re now wondering, “how can the Paketo maintainers make sure I always build with the latest and greatest versions (latest JVM, latest APM agent, etc.)?”

Well, we rely on a lot of automation: we have a pipeline-builder that generates GitHub Actions workflows that execute every week to download and checksum the latest versions, so that the updated buildpack will refer to them. We could have used Renovate (actually there are some discussions about it) or maybe even Dependabot, but I believe our automation predates those tools!

Also, Paketo Buildpacks do not include those dependencies: they are online buildpacks in the sense that if, for example, the detection notices you need Java 21 from Zulu, it will download it (most often from GitHub, verifying the checksum of course). That makes the component buildpacks lighter since they don’t have to include several 200MB+ JDKs!

Paketo Buildpacks: What Do They Allow You to Do Today?

The Java Composite Buildpack

Let’s focus on the Java buildpack, which includes more than 25 component buildpacks! Here are the highlights of the most popular ones:

  • Apache Tomcat, TomEE and Open Liberty buildpacks: provide you with the servlet container of your choice if you set an environment variable or you need a servlet container (in case you just built a war archive)
  • The APM (Application Performance monitoring) buildpacks will not only download the latest version of the wanted agent, but will also configure your app and startup script (which we often call helper) to configure and use them: Azure Application Insights, Datadog, Google Stackdriver (now renamed to Google Cloud Operations)

But there are others available, not included in the Java composite, that you can use too: Elastic, AppDynamics, New Relic, etc. – by simply specifying their buildpack name when you use the pack CLI or the Spring Boot plugins.

Those are great integrations, but maybe all you need is just the OpenTelemetry buildpack that provides you with the agent that can export to the OpenTelemetry Collector or directly to any OTLP/gRPC/HTTP capable backend; to stop relying on a given vendor agent!

  • The JVM buildpack: there is only one in the composite, and it’s BellSoft Liberica

But Azul Zulu, Oracle JDK, GraalVM JDK, Eclipse Adoptium Temurin, Eclipse OpenJ9, Amazon Corretto, SapMachine and Alibaba Dragonwell buildpacks are available simply by specifying their names – more on them in the Java 26 chapter!

  • The packagers buildpacks: to allow you to simply provide source code to the buildpacks: Maven and Gradle of course, but also SBT, Leiningen and Clojure tools for the Scala and Clojure developers!
  • The utilities buildpacks: ca-certificates to allow you to include a custom CA Certificate in the JVM truststore or keystore (at build time or even at runtime); the syft buildpack to run syft against each and every layer (like the JVM layer, app layer, etc.) to provide you with a detailed Software Bill of Materials (SBOM) of the image you built.

I think you get the idea: there’s a buildpack for each need – I’d be surprised if you would ever run into a situation where there is no buildpack for your use case!

The Java-Native-Image Composite Buildpack

There is a second Java composite buildpack, similar to the previous, but focused on the creation of GraalVM-based apps, that will create a native binary of your Java application and set the entrypoint accordingly, thanks to the native-image buildpack

So the JVM and Integrations Can Be Chosen; What About the Base Image?

For the base images, Paketo Buildpacks, by default, rely on either Ubuntu (Bionic, Jammy and Noble) or Redhat (UBI 8, UBI 9 and soon UBI 10) base images. So without creating a new builder yourself, you can out of the box trust Paketo.

Talking about Ubuntu based images, did you know the apt buildpack, to install some system libraries in your image, is now an official Paketo Buildpack? Try it out!

But it’s not that hard to use any OS of your choice as a base image.

You can use the --run-image argument when you build your image, but in that case you would have to make sure that your app will be compatible with a whole different image than the one that was used during build.

buildpacks, builders and stacks

I would rather advise you to create your own builder (a builder defines a list of buildpacks, and the base images at build-time and runtime), it’s not that hard, I have an example based on Chainguard Wolfi distroless (those base images that contain the absolute minimum OS libraries to run your app; perfect to reduce the attack surface)

Paketo Buildpacks and Spring Boot: A Love Story

Spring Boot started integrating (via the maven and gradle plugins to build OCI images) with Paketo Buildpacks since Spring Boot 2.3.0.M1, back in January 2020, approximately at the same time that Pivotal was acquired by VMware.

Even a dedicated spring-boot buildpack was created (I see you Quarkus fans! don’t feel abandoned by this article, there is a Quarkus extension to build using buildpacks, a nice article from one of the maintainers explains it all!).

This buildpack implemented several ideas:

  • injecting the spring-cloud-bindings library, so that Kubernetes Service Binding (an operator injecting environment variables and secrets to your container) can help your app autoconfigure credentials and its data source
  • Spring Generations: to warn the user if they’re relying on an unsupported version of Spring Boot (we have GitHub actions workflows that retrieve the latest support date for all Spring Boot versions)
  • configure less memory / threads for reactive apps; help with Spring Boot native image configuration, etc.

But let’s talk about the latest and most exciting feature: Spring Boot performance.

Spring Boot Performance – Paketo Buildpacks Got You Covered!

What do I mean by Spring Boot performance with Paketo Buildpacks?

  • GraalVM native builds? that has already been implemented along with the native-image buildpack, for quite some time now (since Spring Boot 3, November 2022)
  • CRaC (Coordinated Restore at Checkpoint) builds? well, I have to admit there’s no support for CRaC with Paketo Buildpacks – the issue is that with CRaC when you take a snapshot of the app, you could potentially bake confidential information, like data source credentials, directly into the OCI image… not saying it’s impossible to avoid this, but it was never implemented with buildpacks as far as I know (another limitation of CRaC is that it only works on Linux, but that does not impact Paketo Buildpacks that are only for Linux based images anyway)

No, I mean AOT (Ahead-of-Time) Processing, CDS (Class Data Sharing – now transitioned into AOT Cache since Java 24) and… the simple yet effective unpacked layout.

To benefit from AOT Processing, you have to build your app with process-aot (via the maven or gradle plugin) and then you just tell the buildpacks to include the -Dspring.aot.enabled=true JVM argument at runtime (there’s a shortcut environment variable for that, BP_SPRING_AOT_ENABLED). You may already know that AOT processing is a required step before you can turn your app into a native binary with GraalVM.

Unpack is just turning your Spring Boot app into an unzipped form, which no longer requires the Spring Boot class loader and is necessary for AOT Cache. The command to do that, since Spring Boot 3.3 is: java -Djarmode=tools -jar my-app.jar extract --destination my-app-extracted

AOT Cache (previously CDS) was more complicated to integrate in the buildpack: it requires a training run (you start your app until all beans are loaded and then you shut it down) to generate a caching file that will make your app start significantly faster. The difficulty is that the caching file must be generated in the exact same conditions (same JVM, same permissions, same timestamp even!) to be loaded for the next runs!

Those performance enhancements are real, you can see several benchmarks and see the startup time gets 4 times faster and the app memory consumption was divided by 4!

Have a look at this benchmark from the Spring Blog:

You may wonder why all those performance improvements are not enabled by default… well there are some corner cases (if you use different profiles that include different libraries, if some code tries to connect to a production only resource during training run, etc.) that could at best make the improvements not effective, and at worst break the build…

That said, if you opt in to improvements that are not compatible with your Spring Boot version, the buildpack will know and tell you!

What About Java 26? And After?

Java 26 brings in many new features, but the one feature I believe will have the most impact on the Paketo Buildpacks is AOT Object Caching, enhancing Project Leyden (CDS / AOT Cache) to support all garbage collectors, including ZGC; before that only select garbage collectors could leverage this optimization feature (see, another example you can’t apply automatically those performance improvements).

After Java 26 support is added by a supported vendor, we’ll add this new version to the associated buildpack in under a week BUT we’ll still keep Java 21 as the default version until September 2026 when we’ll have Java 25 set as the default version and Java 26 will be dropped for Java 27 (only Java LTS versions stay for a long time, even maybe… forever?!)

And after Java 26? Well, we know our users want more flexibility with base images (in particular the ability to use any distroless image easily) and also extended SBOM support.

This article is part of the JAVAPRO magazine issue:

From Coder To System Designer

Understand what it means to move from coding to designing systems in the age of AI.
Take a closer look at modern Java platforms, architectural thinking, and the responsibilities that come with shaping complex software systems.

Discover the edition 

Total
0
Shares
Previous Post

BoxLang 1.14.0 : Query Transformers: Take Full Control of Your Query Results

Next Post

BoxLang’s First 11 Months: Explosive Growth, Developer Empowerment, and the Return of Dynamic Language!

Related Posts