DEV Community

AnkitDevCode
AnkitDevCode

Posted on

Getting Started with Quarkus and GraalVM Native Image

Please refer to my previous post for more information about Docker containers

🎯 Objectives

  • ✅ Introduction
  • 🧠 What Is Quarkus?
  • 🧪 What Is GraalVM Native Image?
  • 📦 Creating a Quarkus Project
  • ⚙️ Enabling Native Image Build
  • 📊 Performance Benchmarks
  • 🏁 Conclusion

1. ✅ Introduction

As cloud-native applications demand faster startup times and lower memory consumption, traditional Java stacks often fall short—especially in containerized or serverless environments. Quarkus, a modern, Kubernetes-native Java framework, is designed to bridge this gap by optimizing Java for the cloud.

One of Quarkus’s most powerful features is its integration with GraalVM Native Image, which compiles Java applications into native executables. These executables eliminate the need for a JVM at runtime, dramatically reducing startup time, memory footprint, and cold start latency—making Quarkus an excellent choice for microservices and serverless functions.

In this guide, you’ll learn how to set up a Quarkus project, build a native image using GraalVM, and containerize it with a minimal Docker image ready for production deployment.

2. 🧠 What Is Quarkus?

Quarkus is a modern, Kubernetes-native Java framework designed for building cloud-native applications. Created by Red Hat, it’s optimized for both GraalVM and HotSpot, and built from best-of-breed Java libraries like Hibernate, RESTEasy, and Vert.x.

Its tagline — “Supersonic Subatomic Java” — reflects its goals

Why Use Quarkus?

  • ⚡ Blazing-fast startup times Ideal for microservices, serverless, and scale-to-zero environments
  • 🧠 Low memory footprint Optimized for containers and resource-constrained deployments
  • 🧪 Live reload and dev mode Instant feedback during development without restarting the app
  • 🐳 Kubernetes-native features Auto-generates manifests, integrates with Docker and OpenShift
  • 🧰 Familiar Java standards Built on JAX-RS, CDI, Hibernate — no steep learning curve
  • 🧩 Rich extension ecosystem Plug-and-play support for Kafka, REST, OpenAPI, databases, and more
  • 🛠️ Build-time optimizations Moves reflection and config to build time for leaner runtime
  • 🧬 GraalVM native image support Compile to native binaries for ultra-fast startup and minimal RAM usage
  • 📦 Smaller container images Perfect for CI/CD pipelines and production-grade Docker deployments
  • 📈 Improved performance and scalability Handles high-throughput workloads with minimal overhead

3. 🧪 What Is GraalVM Native Image?

A Native Image is a precompiled, standalone binary of your Java application, created using GraalVM's native-image tool.

Instead of running on the JVM at runtime, your code is compiled ahead-of-time (AOT) into a native executable for your OS and CPU architecture.

✅ Key Characteristics

5. 📦 Creating a Quarkus Project

The simplest way to start a new Quarkus project is by generating it from code.quarkus.io. To begin, select the REST service extension

Build project

 ./mvnw clean install

Enter fullscreen mode Exit fullscreen mode

if you run your Quarkus app like:

java -jar target/quarkus-demo-1.0.0-SNAPSHOT.jar
Enter fullscreen mode Exit fullscreen mode

it won't work unless you've explicitly built a fat JAR with a Main-Class defined.

✅ Correct Ways to Run a Quarkus App (We will use this in the guide)

java -jar target/quarkus-app/quarkus-run.jar
Enter fullscreen mode Exit fullscreen mode

If you really need a fat JAR with a Main-Class:
Enable the "uber-jar" packaging in application.properties or via Maven config.
In application.properties:

quarkus.package.type=uber-jar

Or via Maven command:

./mvnw clean package "-Dquarkus.package.type=uber-jar"

Then you can run it normally:

java -jar target/quarkus-demo-1.0.0-SNAPSHOT.jar

Access via http://localhost:8080/hello

We can see endpoint is up and running now.

6. ⚙️ Enabling Native Image Build

In a typical Quarkus project, you might find multiple Dockerfiles in the src/main/docker/ directory — each with a different purpose, based on how you want to build and run your application.

Common Dockerfiles in a Quarkus Project

Since we're focused on Quarkus Native Images, let's break down the micro native-focused Dockerfiles

# Use minimal Quarkus base image with UBI 9
FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0

# Define working directory
WORKDIR /app

# Ensure proper directory ownership and permissions
RUN mkdir -p /app && \
    chown 1001:root /app && \
    chmod g+rwX /app

# Copy the Quarkus native binary
COPY --chown=1001:root --chmod=0755 target/quarkus-demo-1.0.0-SNAPSHOT-runner /app/application

# expose HTTP port
EXPOSE 8080

# Set the user to a non-root UID
USER 1001

# Run the native executable with proper host binding
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
Enter fullscreen mode Exit fullscreen mode
  • Base Image: This is a minimal Red Hat UBI 9 image created by the Quarkus team.
  • It’s optimized for Quarkus native binaries: no shell, no package manager, very small (~10MB).
  • Comes with a non-root user (UID 1001) pre-defined.
  • Copies the native binary (produced by ./mvnw clean package -Pnative "-Dquarkus.native.container-build=true") into the image

to generate native build, you can find in the pom.xml the following Maven profile section:

<profiles>
    <profile>
        <id>native</id>
        <activation>
            <property>
                <name>native</name>
            </property>
        </activation>
        <properties>
            <skipITs>false</skipITs>
            <quarkus.native.enabled>true</quarkus.native.enabled>
        </properties>
    </profile>
</profiles>
Enter fullscreen mode Exit fullscreen mode

You can build Quarkus native executables in two ways:

  1. Use a Container Image of GraalVM. This option does not require installing GraalVM locally
  2. Install GraalVM locally and use it to build a native executable

1. Native Build Using an In-container build (Quarkus + GraalVM)

✅ Prerequisites

  • Docker or Podman installed
  • JDK 17+ (for local dev, not required inside container)
  • Quarkus project with quarkus-maven-plugin
  • Native profile enabled in pom.xml
./mvnw clean package -Pnative "-Dquarkus.native.container-build=true"

Using the Quarkus CLI:

quarkus build --native -Dquarkus.native.container-build=true

This tells Quarkus to:

  • Use a GraalVM container image internally
  • Compile your app ahead-of-time into a native binary
  • The *-runner file is the built native binary produced by Quarkus.


How to Use a Specific Dockerfile

docker build -f src/main/docker/Dockerfile.native-micro -t quarkus-app:latest .

we can inspect the docker image size and layers of your generated Docker image, you can use the following Docker commands:


runs your Docker image named quarkus-app:latest and maps port 8080 inside the container to port 8080 on your local machine.

 docker run -p 8080:8080 quarkus-app:latest 

Application is up and running

Dockerfile for Native Quarkus Build (Multi-Stage)

.dockerignore

!target/*-runner
!target/*-runner.jar
!target/lib/*
!target/quarkus-app/*
.git
.idea/
*.iml
*.log
*.md
*.bak
Enter fullscreen mode Exit fullscreen mode

Dockerfile

# Stage 1: Build
FROM quay.io/quarkus/ubi9-quarkus-mandrel-builder-image:jdk-17 AS build
COPY --chown=quarkus:quarkus --chmod=0755 mvnw /project/mvnw
COPY --chown=quarkus:quarkus .mvn /project/.mvn
COPY --chown=quarkus:quarkus pom.xml /project/
USER quarkus
WORKDIR /project
# Make Maven wrapper executable and download dependencies
RUN chmod +x ./mvnw && ./mvnw dependency:go-offline
# Copy source files and build
COPY src /project/src
RUN ./mvnw package -Pnative -DskipTests
#RUN rm -rf target
RUN chmod +x target/*-runner

# Stage 2: Runtime
FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0
# Define working directory
WORKDIR /app

RUN chown 1001 /app  && chmod "g+rwX" /app && chown 1001:root /app

# Copy built JAR from builder stage
COPY --from=build  --chown=1001:root --chmod=0755  /project/target/*-runner /app/application

# Switch to non-root user
USER 1001
# expose HTTP port
EXPOSE 8080
# Run the native executable with proper host binding
ENTRYPOINT ["/app/application", "-Dquarkus.http.host=0.0.0.0"]
Enter fullscreen mode Exit fullscreen mode


2. To build a Quarkus native image using your local GraalVM installation

✅ Prerequisites

  • A local installation of Mandrel or GraalVm, correctly configured according to the Building a native executable guide.
  • Additionally, for a GraalVM installation, native-image must also be installed.

✅ Use GraalVM CE or Oracle GraalVM
Download:
GraalVM CE (GitHub)

After installing GraalVM, install the native-image tool:

bash

gu install native-image
Enter fullscreen mode Exit fullscreen mode

Verify installation:

native-image --version
Enter fullscreen mode Exit fullscreen mode
java -version
Enter fullscreen mode Exit fullscreen mode

It should show GraalVM as your JVM.

Clean and Build with Maven
In your Quarkus project:

./mvnw clean package -Pnative
Enter fullscreen mode Exit fullscreen mode

This will:

Use the local GraalVM's native-image tool and Compile your app into a native executable at:

target/quarkus-demo-1.0.0-SNAPSHOT-runner
Enter fullscreen mode Exit fullscreen mode

Make sure it’s executable:

chmod +x target/quarkus-demo-1.0.0-SNAPSHOT-runner
Enter fullscreen mode Exit fullscreen mode

The *-runner file is the built native binary produced by Quarkus.

Run the native executable:

./target/quarkus-demo-1.0.0-SNAPSHOT-runner -Dquarkus.http.host=0.0.0.0
Enter fullscreen mode Exit fullscreen mode

8. 📊 Performance Benchmarks

Here’s an overview of performance benchmarks for Quarkus native builds compared to JVM builds, based on community and official Quarkus data



Explanation:

  • Startup time is dramatically improved, making native ideal for serverless, microservices, and fast-scaling scenarios.
  • Memory footprint shrinks considerably because native images don’t load the JVM runtime.
  • Throughput may be slightly lower because some JIT optimizations are not available at runtime.
  • The trade-off is typically worthwhile when fast startup and low memory are priorities.

Resources for Benchmarking

Quarkus Native Image Best Practices

Quarkus Benchmark Reports

Quarkus vs Spring Native Benchmark

9. 🏁 Conclusion

Quarkus, paired with GraalVM native image, offers a powerful solution for building fast, lightweight, and cloud-optimized Java applications. With near-instant startup times, drastically reduced memory usage, and minimal container footprints, it’s a game-changer for microservices, serverless, and edge computing.

When to Use Native Image?

  • Serverless functions (AWS Lambda, Azure Functions, etc.)
  • Microservices needing rapid scale-up and low resource use
  • Apps where startup latency is critical
  • When reducing memory consumption is important

Caveats

  • Native images take longer to build and require GraalVM tooling.
  • Debugging native apps is harder than JVM.
  • Some libraries/frameworks may need extra config or might not fully support native.

References

🧠 AI Assistance — Content and explanations are partially supported by ChatGPT, Microsoft Copilot, and GitLab Duo, following Quarkus and Docker best practices and OCI standards.

Top comments (0)