How do you customize which Paketo builder is used and control things like the JVM version or JVM buildpack settings during a buildpacks build?
answer
- builder + runImage override
- -tiny / -base / -full variants
- environment map -> BP_* build-time vars
- BP_JVM_VERSION pins JRE, BP_NATIVE_IMAGE=true
- BPE_*/BPL_* runtime; memory calculator
basics
~10 sOverride 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 sThe 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 linesimport 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
Aware you can override the builder and set BP_JVM_VERSION.
Use the environment map for JVM version/type and pick a builder variant.
Reason about -tiny/-base/-full trade-offs, native builds, and the memory calculator.
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