skip to content

What is the GraalVM native-maven-plugin and how do you use it to build a native image of a Spring Boot app with Maven?

level: juniorimportance: must knowfreq 60%

answer

  1. org.graalvm.buildtools:native-maven-plugin
  2. mvn -Pnative native:compile
  3. process-aot runs first, then native-image
  4. needs GraalVM JDK, closed-world
  5. profile predeclared by spring-boot-starter-parent

basics

~10 s

It's the GraalVM native-build-tools Maven plugin that compiles your app into a native executable. In Spring Boot you run mvn -Pnative native:compile; the native profile plus its native:compile goal invoke GraalVM's native-image compiler.

solid answer

~40 s

The `native-maven-plugin` (groupId `org.graalvm.buildtools`) drives GraalVM `native-image` from Maven. Spring Boot's parent POM predeclares and configures it inside a `native` profile, so you build with `mvn -Pnative native:compile`. That produces an ahead-of-time-compiled standalone executable (no JVM at runtime) with fast startup and low memory. Under the hood the plugin first triggers Spring Boot's `process-aot` goal (AOT processing that generates bean-registration code and reachability hints), then hands the classpath to GraalVM's `native-image` builder. You need a GraalVM JDK installed. The plugin also has an `add-reachability-metadata` goal that pulls in community metadata. `native:compile` forks the lifecycle up to `package`; the profile also binds `compile-no-fork` to the `package` phase so `mvn -Pnative package` builds the binary too.

code

java · 17 lines
java
// pom.xml inherits the `native` profile from spring-boot-starter-parent:
//
//   <parent>
//     <groupId>org.springframework.boot</groupId>
//     <artifactId>spring-boot-starter-parent</artifactId>
//   </parent>
//
// Build the native executable:
//   $ mvn -Pnative native:compile
//
// Run it (no JVM needed):
//   $ ./target/myapp
//
// The build performs two stages automatically:
//   1) spring-boot:process-aot  -> generates bean code + META-INF/native-image hints
//   2) native:compile           -> GraalVM native-image links a standalone binary
public class App { /* standard @SpringBootApplication */ }

go deeper

for a junior

Know the command mvn -Pnative native:compile and that it produces a JVM-free executable with fast startup.

for a middle

Explain the two-stage build (process-aot then native-image) and that the profile is inherited from Spring Boot's parent.

for a senior

Discuss closed-world constraints, GraalVM JDK requirement, and platform-specific output / containerized Linux builds.

for a principal

Weigh native vs JVM tradeoffs at an architecture level (startup/memory vs throughput/iteration) and own the CI strategy for slow native builds.

## What it is `native-maven-plugin` is part of **GraalVM native-build-tools** (Maven coordinates `org.graalvm.buildtools:native-maven-plugin`). It is the Maven front-end for GraalVM's `native-image` tool, which performs **AOT (ahead-of-time) compilation**: instead of shipping bytecode that a JVM JIT-compiles at runtime, it statically analyzes your closed-world application and compiles it into a single OS-native executable. The result starts in tens of milliseconds and uses far less memory than a JVM, at the cost of longer build times and a closed-world assumption (all reflection/resources/proxies must be known at build time). ## How Spring Boot wires it You do **not** configure the plugin yourself for the common case. `spring-boot-starter-parent` (and the Spring Boot BOM) declares a Maven **`native` profile** that: 1. Configures `org.graalvm.buildtools:native-maven-plugin` with sensible defaults. 2. Binds Spring Boot's `spring-boot-maven-plugin:process-aot` goal so AOT processing runs first. 3. Enables the GraalVM reachability **metadataRepository**. So the canonical commands are: - `mvn -Pnative native:compile` — build the native executable. - `mvn -Pnative package` — same via the `compile-no-fork` binding on the `package` phase. - `mvn -PnativeTest test` — run tests as a native image. ## The two-step build (why order matters) A Spring Boot native build is really two stages: 1. **`process-aot`** (Spring Boot's AOT engine) runs the application context at build time, then emits generated Java source for bean definitions plus `reflect-config.json`, `resource-config.json`, proxy config, etc. under `META-INF/native-image`. 2. **`native:compile`** feeds the compiled classes + generated code + metadata to GraalVM `native-image`. Step 1 must precede step 2, otherwise the image lacks the hints and fails at runtime. Spring Boot's profile arranges this ordering for you. ## Goals of the plugin - `native:compile` — build the executable (forks lifecycle to `package`). - `native:compile-no-fork` — same without forking (bound in the profile). - `native:add-reachability-metadata` — pull metadata from the GraalVM Reachability Metadata Repository into the build. - `native:test` / build-and-run tests natively. - `native:metadata-copy` — copy collected metadata (from the agent) into your project. ## Prerequisites & gotchas - You must build with a **GraalVM JDK** (or a distribution bundling `native-image`); a plain OpenJDK cannot compile a native image. - Native builds are slow (minutes) and memory-hungry — don't run them on every commit. - Reflection/resources not discovered by Spring AOT or the metadata repo must be supplied via `RuntimeHints` / `@RegisterReflectionForBinding` or via a tracing-agent run. - The executable is platform-specific: build on/for the target OS+arch (commonly inside a container for Linux images). ## When to use Use native images when startup latency and memory footprint dominate (serverless/FaaS, CLI tools, scale-to-zero). Stick with the JVM when you need peak sustained throughput, dynamic class loading, or fast iteration, since JIT beats AOT on long-running throughput and native builds slow the dev loop.

  • Why can't you build a native image with a regular OpenJDK?
    GraalVM `native-image` is the AOT compiler, and it ships only with a GraalVM (or GraalVM-based) JDK. A stock OpenJDK has no `native-image` binary, so the plugin cannot compile. You install a GraalVM JDK (e.g. via a build like Liberica NIK or Oracle GraalVM) so `native-image` is on the toolchain.
  • What runs before native:compile in a Spring Boot native build, and why?
    Spring Boot's `spring-boot-maven-plugin:process-aot` goal. It executes AOT processing to generate bean-registration source and reachability metadata (reflect/resource config) so the closed-world native-image build has all the hints it needs; running it after compilation would be too late.

saying these in an interview costs you the question

  • Thinking `native:compile` alone works without Spring's AOT processing step
  • Believing you can produce a native image with a plain OpenJDK
  • Assuming the native executable still needs a JVM installed to run
  • Thinking native images are always faster than the JVM (they win on startup/memory, not sustained throughput)

context