Enterprise Java

GraalVM Native Image for Spring Boot / Quarkus: Step-by-step production example

If you’ve heard that Java can start in milliseconds and sip memory like an espresso shot—yep, that’s GraalVM Native Image. It compiles your app ahead-of-time into a tiny, self-contained binary that’s perfect for microservices, serverless, and CLI tools. Below are pragmatic, production-ready walkthroughs for both Spring Boot and Quarkus, sprinkled with gotchas, DX tips, and ops checklists.

What you’ll need (and a few realities)

  • A recent JDK (Java 21+ is ideal), Docker/Podman for containerized builds, and enough horsepower: native builds are heavier than JVM jars. Quarkus recommends at least 4 CPUs and 4GB RAM when building in containers.
  • You can build with framework tooling (Spring Boot AOT/native) or directly with the GraalVM Native Build Tools. Frameworks layer smart defaults on top of GraalVM to smooth over reflection, resources, and proxies.

Path A: Spring Boot → Native image (production example)

We’ll package a simple HTTP service with Actuator, ready for containers and K8s.

1) Generate the project

Start from Spring Initializr with Spring Web, Actuator, and (optionally) Spring Data JPA if you’re hitting a database. Spring Boot includes native support with AOT processing and build plugins.

2) Add a tiny endpoint

@RestController
class HelloController {
  @GetMapping("/hello")
  String hello() { return "Hi from native 👋"; }
}

3) Build a native container image (recommended for prod)

Boot’s plugin integrates with Buildpacks so you don’t have to handcraft a Dockerfile:

./mvnw -Pnative spring-boot:build-image
# or Gradle:
./gradlew bootBuildImage --imageName=ghcr.io/acme/hello-native -Pnative

3) Build a native container image (recommended for prod)

Boot’s plugin integrates with Buildpacks so you don’t have to handcraft a Dockerfile:

./mvnw -Pnative spring-boot:build-image
# or Gradle:
./gradlew bootBuildImage --imageName=ghcr.io/acme/hello-native -Pnative

This produces a Linux container that already contains a GraalVM-compiled binary. It’s the simplest way to get a production image that works consistently across environments.

4) Or: build a local native binary

./mvnw -Pnative -DskipTests native:compile
# binary ends up in target/ (e.g., target/your-app)

Use this when you want to craft your own Dockerfile or run bare-metal.

5) Minimal Dockerfile (if you built the binary yourself)

FROM scratch
COPY target/your-app /app
EXPOSE 8080
ENTRYPOINT ["/app"]
docker run --rm -p 8080:8080 ghcr.io/acme/hello-native
curl :8080/hello

7) Health, metrics, readiness

Because you added Actuator, expose only the endpoints you need and wire liveness/readiness for K8s. The native binary doesn’t change your Actuator semantics—just your startup time.

Debug tip: If you hit reflection/resource errors during native build, add Spring Native hints or supply metadata—see the Reachability Metadata primer and Spring’s native docs.

Path B: Quarkus → Native image (production example)

Quarkus leans hard into GraalVM: fast dev mode, thoughtful defaults, and great native ergonomics.

1) Create & code

Use the Quarkus CLI or Maven to scaffold with REST:

quarkus create app com.acme:native-demo --extension='rest,container-image-docker'
cd native-demo

Add a resource:

@Path("/hello")
public class HelloResource {
  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String hello() { return "Hello from Quarkus native"; }
}

2) Build a native executable (local)

./mvnw package -DskipTests -Dnative
# runner at target/*-runner

3) Build in a container (consistent & portable)

./mvnw package -DskipTests -Dnative -Dquarkus.native.container-build=true

To pin the builder, add in application.properties:

quarkus.native.container-build=true
quarkus.native.builder-image=quay.io/quarkus/ubi9-quarkus-mandrel-builder-image:jdk-21
quarkus.container-image.build=true
quarkus.container-image.group=ghcr.io/acme

This uses the Mandrel/GraalVM builder inside UBI and produces a container image you can push to your registry. Quarkus recommends provisioning ≥4 CPUs / 4GB RAM to keep native builds reliable. Quarkus

4) Run the image

docker run --rm -p 8080:8080 ghcr.io/acme/native-demo:1.0.0
curl :8080/hello

5) Production flags you’ll actually use

  • -Dquarkus.native.additional-build-args=--native-image-info to surface build details.
  • -Dquarkus.native.native-image-xmx=5g if build memory is tight (slower but steadier).
  • Quarkus’ Native Reference Guide has a goldmine of troubleshooting advice for OOMs, link-at-build-time issues, and GC/runtime trade-offs.

CI/CD: fast, hermetic pipelines

Why containers for builds? Consistency. Both frameworks can produce native images entirely inside Docker/Podman, which means no GraalVM installation on runners.

GitHub Actions sketch (Spring Boot, buildpacks):

name: native-image
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - name: Build native container
        run: ./mvnw -Pnative spring-boot:build-image -DskipTests
      - name: Push
        run: docker tag docker.io/library/your-app:latest ghcr.io/acme/your-app:$(git rev-parse --short HEAD) &&
             echo "$CR_PAT" | docker login ghcr.io -u USERNAME --password-stdin &&
             docker push ghcr.io/acme/your-app:$(git rev-parse --short HEAD)

GitHub Actions sketch (Quarkus, container build):

- name: Build native in container
  run: ./mvnw package -DskipTests -Dnative -Dquarkus.native.container-build=true

Observability & ops notes

  • Images are tiny and start fast, but JVM mode may still win on some long-running throughput benchmarks due to JIT optimizations. Pick native for cold-start/scale-to-zero and tight resource envelopes; use JVM for hot, CPU-bound workloads when peak throughput wins.
  • Actuator (Boot) and SmallRye/Micrometer (Quarkus) still work—just ensure any exporters you use (e.g., Prometheus) are included at build time.
  • Memory tuning differs from the JVM. Review Quarkus’ native memory guidance and GraalVM docs before setting hard limits in containers.

Troubleshooting checklist (works for both)

  1. Reflection & resources: Supply Reachability Metadata or use framework hints. The GraalVM build tools can also auto-collect metadata with the tracing agent.
  2. Third-party libs: Prefer libraries with native support; otherwise add reflect-config.json/resource entries or code-level hints.
  3. Build OOMs/timeouts: Reduce parallelism, set a build Xmx (Quarkus: -Dquarkus.native.native-image-xmx=...), or scale your CI runner.
  4. Different base images: If you need musl/static executables for Alpine or scratch, you’ll pass extra native-image flags; validate with your distro first. (Quarkus and GraalVM docs outline libc choices and constraints.)

Copy-paste “first deploy” recipes

Spring Boot (Buildpacks):

./mvnw -Pnative spring-boot:build-image \
  -Dspring-boot.build-image.imageName=ghcr.io/acme/hello-native:$(git rev-parse --short HEAD)

docker run --rm -p 8080:8080 ghcr.io/acme/hello-native:$(git rev-parse --short HEAD)

Quarkus (Container native build):

./mvnw package -DskipTests -Dnative -Dquarkus.native.container-build=true
docker run --rm -p 8080:8080 ghcr.io/acme/native-demo:1.0.0

Useful links

Final nudge

Start with containerized native builds (they’re reproducible), bake in health checks early, and keep an eye on memory during builds. Once you see a cold-start in milliseconds for real, it’s hard to go back.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button