skip to content

How do you customize which Paketo builder is used and control things like the JVM version or JVM buildpack settings during a buildpacks build?

level: seniorimportance: should knowfreq 45%

answer

  1. builder + runImage override
  2. -tiny / -base / -full variants
  3. environment map -> BP_* build-time vars
  4. BP_JVM_VERSION pins JRE, BP_NATIVE_IMAGE=true
  5. BPE_*/BPL_* runtime; memory calculator

basics

~10 s

Override the builder (e.g. builder = "paketobuildpacks/builder-jammy-base") and/or runImage. Pass build-time settings to the buildpacks via environment map entries, e.g. BP_JVM_VERSION=21 to pick the JRE version.

solid answer

~40 s

The build is driven by a **builder** image (bundles buildpacks + lifecycle) and a **run** image (the app's base). Override them with `builder.set(...)` / `runImage.set(...)` (Gradle) or `<image><builder>`/`<runImage>` (Maven) — e.g. switch from the default `-tiny` to `paketobuildpacks/builder-jammy-base` when you need a shell or more OS packages. You configure the buildpacks themselves through the `environment` map, which sets `BP_*` (build-time) and `BPE_*`/`BPL_*` (runtime) variables. Common ones: `BP_JVM_VERSION` to pin the JRE major version, `BP_JVM_TYPE` (JRE vs JDK), `BP_NATIVE_IMAGE=true` to produce a GraalVM native image (with the native buildpack), and `BPE_*` to append JVM/runtime env. You can also select specific buildpacks via `buildpacks.set([...])`. These give fine control without abandoning the no-Dockerfile model.

code

kotlin · 16 lines
kotlin
import org.springframework.boot.gradle.tasks.bundling.BootBuildImage

tasks.named<BootBuildImage>("bootBuildImage") {
    // Swap the minimal -tiny builder for -base (has a shell + more libs).
    builder.set("paketobuildpacks/builder-jammy-base")
    runImage.set("paketobuildpacks/run-jammy-base")

    environment.set(
        mapOf(
            "BP_JVM_VERSION" to "21",          // pin the JRE major version in the image
            "BP_JVM_TYPE" to "JRE",             // smaller than a full JDK
            // Bake a runtime JVM flag into the image (BPE_ = build-time-set env):
            "BPE_APPEND_JAVA_TOOL_OPTIONS" to "-XX:MaxRAMPercentage=75.0",
        ),
    )
}

go deeper

for a junior

Aware you can override the builder and set BP_JVM_VERSION.

for a middle

Use the environment map for JVM version/type and pick a builder variant.

for a senior

Reason about -tiny/-base/-full trade-offs, native builds, and the memory calculator.

for a principal

Set org-wide builder/runtime standards, pin digests, and decide JVM-vs-native policy.

**Two images per build:** - **Builder image** — contains the ordered set of **buildpacks** plus the CNB **lifecycle** that runs detect/build. Spring Boot defaults to a Paketo builder (recent Boot uses `paketobuildpacks/builder-jammy-java-tiny`). Variants: `-tiny` (distroless-ish, minimal, no shell — smallest/most secure), `-base` (Ubuntu Jammy with a shell and common libs), `-full` (more packages, for apps needing them). - **Run image** — the base image the finished app actually runs on. Each builder has a matching default run image; you can override it independently. **Overriding them:** - Gradle: `builder.set("paketobuildpacks/builder-jammy-base")`, `runImage.set("paketobuildpacks/run-jammy-base")`. - Maven: `<image><builder>paketobuildpacks/builder-jammy-base</builder><runImage>...</runImage></image>`. Use `-base`/`-full` when a library needs `glibc`/a shell/native deps that `-tiny` lacks, or when you must run something in the container beyond `java`. **Configuring the buildpacks — the `environment` map:** Buildpacks read configuration from environment variables at build time. Spring Boot exposes an `environment` (Gradle) / `<env>` (Maven) map on the task: - `BP_JVM_VERSION` — pick the **JRE major version**, e.g. `21` or `17`. Without it the buildpack picks a default. This is the canonical way to control the runtime Java version of the image (independent of the toolchain that compiled the code). - `BP_JVM_TYPE` — `JRE` (default, smaller) or `JDK` (needed if the app spawns compilers/tools). - `BP_NATIVE_IMAGE=true` — with the Paketo **native-image** buildpack, produces a GraalVM native executable image instead of a JVM one (pairs with Spring's AOT/native support). - `BP_JVM_CDS_ENABLED` / CDS-related flags — enable Class Data Sharing for faster startup (availability depends on buildpack version). - `BPE_*` — **build-time-set runtime env**: e.g. `BPE_APPEND_JAVA_TOOL_OPTIONS=-XX:+UseZGC` or `BPE_DELIM_...` to inject env vars baked into the image. - `BPL_*` — variables consumed by **launch** buildpacks at container start (e.g. `BPL_JVM_THREAD_COUNT` influences the memory calculator). **Selecting specific buildpacks:** `buildpacks.set(listOf("paketobuildpacks/java", ...))` (Gradle) / `<buildpacks>` (Maven) lets you pin or reorder the buildpacks rather than using the builder's full default order — useful to force the native buildpack or drop ones you don't need. **Memory calculator gotcha:** Paketo's Java buildpack runs a **memory calculator** at container start that sizes heap/metaspace from the container's memory limit and `BPL_JVM_THREAD_COUNT`. If you set `-Xmx` yourself via `JAVA_TOOL_OPTIONS`, you can conflict with it. Prefer the buildpack's knobs (`BPL_*`) or set an explicit limit and understand the calculator. **Reproducibility gotcha:** Using a floating builder tag (`:latest`/rolling `builder-jammy-...`) means your image contents (JRE patch level, OS packages) can drift between builds. For reproducible/audited builds, pin the builder to a **digest** (`paketobuildpacks/builder-jammy-base@sha256:...`). **When to reach for this:** pinning the Java version in the image, going native, adding OS capabilities via `-base`/`-full`, or injecting runtime tuning — all while keeping the no-Dockerfile workflow.

  • How do you set the JVM version of the produced image, and is it the same as your Gradle/Maven toolchain?
    Set `BP_JVM_VERSION` in the environment map. It's independent of the build toolchain: the toolchain compiles your bytecode, while BP_JVM_VERSION picks the JRE that runs it inside the image.
  • What does the Paketo memory calculator do and how can it surprise you?
    At container start it computes heap/metaspace/etc. from the container memory limit and thread count. If you also pass -Xmx via JAVA_TOOL_OPTIONS you can conflict with or override it, causing unexpected OOMs; prefer BPL_* knobs and a correct memory limit.

saying these in an interview costs you the question

  • Thinking BP_JVM_VERSION changes the compile-time toolchain
  • Believing you must write a Dockerfile to change the Java version
  • Assuming -tiny has a shell (it's minimal/distroless-like)
  • Ignoring the memory calculator and hard-coding -Xmx blindly

context