How does building a native image with bootBuildImage differ from compiling one with the GraalVM native-build-tools plugin locally? When would you pick each?
answer
- nativeCompile = local GraalVM, bare binary
- bootBuildImage = GraalVM in builder, OCI image out
- Both need Spring AOT hints
- GraalVM plugin auto-enables native for bootBuildImage
- container daemon vs local GraalVM
basics
~20 sbootBuildImage runs GraalVM native-image inside a Paketo buildpack container, so you don't need GraalVM installed and you get a ready-to-run OCI image. The nativeCompile task runs native-image on your local GraalVM and produces a bare executable, not a container.
solid answer
~40 sBoth produce a GraalVM native executable and both rely on Spring AOT to generate reflection/resource hints. The difference is the toolchain and the output. The `org.graalvm.buildtools.native` plugin's `nativeCompile` task runs `native-image` on a **locally installed GraalVM JDK**, emitting a bare OS-native binary under `build/native/`. `bootBuildImage` with `BP_NATIVE_IMAGE=true` instead runs the whole pipeline **inside a Paketo builder container** — GraalVM ships in the builder image, so no local GraalVM is required, and the result is a small **OCI container image** ready to push and run. Choose bootBuildImage when you want a container and don't want to manage GraalVM on every machine/CI node; choose the local plugin when you need a raw executable, want faster iteration without a container round-trip, or run in an environment without a container daemon.
code
kotlin · 8 lines// Applying the GraalVM plugin auto-enables native for bootBuildImage,
// AND gives you a local nativeCompile task.
plugins {
id("org.springframework.boot") version "3.4.0"
id("org.graalvm.buildtools.native") version "0.10.3"
}
// ./gradlew nativeCompile -> build/native/nativeCompile/<app> (needs local GraalVM)
// ./gradlew bootBuildImage -> OCI image with native binary (GraalVM inside builder)go deeper
Know one uses local GraalVM (bare binary), the other uses a buildpack container (OCI image).
Explain the toolchain location, output type, and that both share Spring AOT.
Discuss the auto-enable behavior when the GraalVM plugin is applied and the no-cross-compile constraint.
Reason about toolchain governance, reproducibility, and CI topology when standardizing native builds across teams.
**Shared foundation.** Both approaches use **GraalVM `native-image`** and both depend on **Spring AOT** — the `process-aot`/`ProcessAot` step that runs your `ApplicationContext` at build time to emit generated bean-definition code plus **reachability metadata** (reflection, resources, serialization, JNI, proxies) that native-image needs because it does closed-world static analysis. So the correctness characteristics (need for hints, no runtime classpath scanning, no runtime bytecode generation) are identical. **Path A — `org.graalvm.buildtools.native` (GraalVM Native Build Tools).** Applying this Gradle/Maven plugin adds tasks like `nativeCompile` and `nativeTest`. `nativeCompile` invokes `native-image` using the **GraalVM installed on the build machine** (resolved via the Java toolchain or `GRAALVM_HOME`/`JAVA_HOME`). Output: a **standalone native executable** in `build/native/nativeCompile/`. No container is produced. You must have a GraalVM JDK present on every environment that runs the compile. `nativeTest` can even run your JUnit suite as a native image. **Path B — `bootBuildImage` + `BP_NATIVE_IMAGE=true`.** The Spring Boot plugin hands the project to **Paketo buildpacks** running in a **builder container**. The **GraalVM toolchain lives inside the builder image**, so the local machine only needs a **container runtime**. The buildpack runs AOT and native-image, then wraps the binary in a minimal run image. Output: an **OCI image** you can `docker run` or push to a registry. Notably, when you apply the `org.graalvm.buildtools.native` plugin, the Spring Boot plugin **auto-configures bootBuildImage to build a native image** (it sets the native path for you), so the two often coexist. **Trade-offs / when to pick each.** - Pick **bootBuildImage** when: you want a deployable container as the artifact; you don't want to install/patch GraalVM across dev laptops and CI runners (the version is centralized in the builder image); you value reproducibility from a curated builder. - Pick **local nativeCompile** when: you need the raw executable (e.g. embedding, non-container deploy, serverless custom runtime); you want the tightest edit-compile loop without container overhead; your environment has GraalVM but no container daemon; or you need `nativeTest`. **Gotchas.** (1) bootBuildImage requires a **running container daemon**; nativeCompile does not. (2) The builder pins a specific GraalVM version — to bump it you change the builder image, not a local install. (3) Both are slow and memory-hungry; the container path adds image pull/export overhead. (4) The container build compiles for the **builder's architecture** — no cross-compilation, so building an arm64 image typically needs an arm64 builder/host.
- If you apply the GraalVM native plugin, do you still need BP_NATIVE_IMAGE=true?No. When the org.graalvm.buildtools.native plugin is applied, the Spring Boot plugin automatically configures bootBuildImage to build a native image, so the buildpack native path is enabled for you. Setting BP_NATIVE_IMAGE=true explicitly is redundant but harmless.
- Which approach lets you run your tests as a native image?The local GraalVM native-build-tools plugin, via its nativeTest task. bootBuildImage only produces the runtime application image.
saying these in an interview costs you the question
- Saying bootBuildImage requires a local GraalVM install
- Claiming nativeCompile produces a container image
- Thinking the two can't coexist in one build