Build Native Java Applications with Quarkus and GraalVM

by Didin J. on Aug 17, 2025 Build Native Java Applications with Quarkus and GraalVM

Learn how to build native Java apps with Quarkus and GraalVM. Create REST APIs, compile to native executables, and compare JVM vs native performance.

In today’s world of cloud-native and serverless applications, startup time and memory usage are critical. Traditional Java applications can be slow to boot and consume large amounts of memory, which makes them less ideal for microservices and serverless environments.

Quarkus, often called the “Supersonic Subatomic Java Framework,” solves this problem by optimizing Java for containers, cloud, and Kubernetes. Combined with GraalVM, you can compile your Quarkus applications into native executables that start in milliseconds and use significantly less memory.

In this tutorial, you’ll learn step by step how to:

  • Create a Quarkus application.

  • Run it in JVM mode.

  • Compile it into a native executable using GraalVM.

  • Compare performance between JVM and native modes.

By the end, you’ll have a blazing-fast Java app ready for modern cloud environments.

Prerequisites

Before we begin, make sure you have:

  • Java 21 (or the latest LTS).

  • Apache Maven 3.9+ installed.

  • GraalVM 24.x installed (with native-image installed).

  • Quarkus CLI (optional but recommended).

  • A code editor like IntelliJ IDEA or VS Code.


Project Setup

You can create a new Quarkus project in two ways:

1. Using Quarkus CLI

quarkus create app com.djamware:quarkus-graalvm-app \
  --extension=resteasy-reactive \
  --no-code
cd quarkus-graalvm-app

2. Using Maven (without CLI)

mvn io.quarkus.platform:quarkus-maven-plugin:3.24.5:create \
    -DprojectGroupId=com.djamware \
    -DprojectArtifactId=quarkus-graalvm-app \
    -Dextensions="resteasy-reactive"
cd quarkus-graalvm-app

This creates a new Quarkus application with RESTEasy Reactive for building REST APIs.


Creating a REST Endpoint

Inside the generated project, Quarkus provides a default resource. Let’s create our own REST endpoint instead.

  1. Navigate to the source folder: 
src/main/java/com/djamware
  1. Create a new file called GreetingResource.java:
package com.djamware;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/hello")
public class GreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello from Quarkus with GraalVM!";
    }
}

This simple REST API will respond with a greeting message when accessed at http://localhost:8080/hello.


Running in JVM Mode

By default, Quarkus applications run in JVM mode, which is ideal for development.

Run the application with:

./mvnw quarkus:dev

Output:

  • Quarkus will start in development mode.

  • You should see something like:

INFO  [io.quarkus] (Quarkus Main Thread) quarkus-graalvm-app 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.15.2) started in 1.234s. Listening on: http://localhost:8080


Testing the Endpoint

Open a browser or use curl:

curl http://localhost:8080/hello

Response:

Hello from Quarkus with GraalVM!

That’s it — your Quarkus REST endpoint is running in JVM mode.

👉 Next, we can proceed with Installing and Configuring GraalVM to prepare for building the native executable.


Installing and Configuring GraalVM

Quarkus integrates seamlessly with GraalVM, but you’ll need GraalVM installed to compile your app into a native executable.

Step 1: Download GraalVM

  • Go to the GraalVM releases page.

  • Download GraalVM JDK 21 (LTS) for your operating system.

Step 2: Install GraalVM

Unpack the downloaded archive and set it as your Java home. For example:

Linux/macOS

tar -xzf graalvm-jdk-21_linux-x64_bin.tar.gz
export GRAALVM_HOME=$PWD/graalvm-jdk-21
export PATH=$GRAALVM_HOME/bin:$PATH
export JAVA_HOME=$GRAALVM_HOME

Windows (PowerShell)

setx GRAALVM_HOME "C:\graalvm-jdk-21"
setx PATH "%GRAALVM_HOME%\bin;%PATH%"
setx JAVA_HOME "%GRAALVM_HOME%"

Step 3: Install Native Image Tool

Once GraalVM is set up, install the native-image tool:

gu install native-image

Verify installation:

java -version
native-image --version


Building a Native Executable

Now that GraalVM is ready, let’s compile the Quarkus app into a native executable.

Run the following Maven command:

./mvnw package -Pnative

Quarkus will:

  • Build your project.

  • Use GraalVM’s native-image tool to compile it.

  • Generate an executable in the target/ folder.

You’ll find it here:

target/quarkus-graalvm-app-1.0.0-SNAPSHOT-runner


Running the Native Application

Run the native binary directly (no JVM needed):

./target/quarkus-graalvm-app-1.0.0-SNAPSHOT-runner

You’ll see output similar to:

INFO  [io.quarkus] (main) quarkus-graalvm-app 1.0.0-SNAPSHOT native (powered by Quarkus 3.15.2) started in 0.015s. Listening on: http://0.0.0.0:8080

👉 Notice how startup time is in milliseconds compared to seconds in JVM mode.


Testing the Native Endpoint

Use curl again:

curl http://localhost:8080/hello

Response:

Hello from Quarkus with GraalVM!

✅ Now your Quarkus app is running as a native executable with lightning-fast startup and reduced memory footprint.


Performance Comparison: JVM vs Native

One of the biggest advantages of Quarkus with GraalVM is the dramatic difference in startup time and memory usage between a JVM-based app and a native executable. Let’s see it in action.

1. Startup Time

Run the application in JVM mode:

./mvnw quarkus:dev

Output (example):

quarkus-graalvm-app ... started in 1.234s. Listening on: http://localhost:8080

Now run the native executable:

./target/quarkus-graalvm-app-1.0.0-SNAPSHOT-runner

Output (example):

quarkus-graalvm-app ... started in 0.015s. Listening on: http://localhost:8080

✅ Native startup time is often 100x faster than JVM mode.

2. Memory Usage

Check memory usage with the ps command (Linux/macOS) or tasklist (Windows).

JVM mode:

ps -o pid,rss,comm -p $(pgrep -f quarkus-graalvm-app)

Example result:

PID   RSS   COMMAND
1234  120000 java

(~120 MB)

Native mode:

ps -o pid,rss,comm -p $(pgrep -f quarkus-graalvm-app-1.0.0-SNAPSHOT-runner)

Example result:

PID   RSS   COMMAND
5678  15000 quarkus-graalvm-app-1.0.0-SNAPSHOT-runner

(~15 MB)

✅ Native mode reduces memory consumption by up to 80–90%.

3. Container Image Size (Optional)

If you build container images:

  • JVM Mode Image: ~200 MB (including JVM).

  • Native Mode Image: ~30 MB (just the binary + minimal base image).

This makes native executables perfect for microservices and serverless platforms where cold starts and resource efficiency matter most.

Summary of JVM vs Native

Feature JVM Mode Native Mode
Startup Time ~1–2 seconds ~10–20 milliseconds
Memory Usage 100–200 MB 10–30 MB
Image Size ~200 MB ~30 MB

👉 With these optimizations, Quarkus + GraalVM makes Java cloud-native ready without sacrificing developer productivity.


Conclusion

In this tutorial, you learned how to:

  • Create a Quarkus project and build a simple REST API.

  • Run the application in JVM mode for fast development.

  • Install and configure GraalVM to enable native builds.

  • Compile the application into a native executable.

  • Compare the performance between JVM vs Native modes.

The results are clear: Quarkus + GraalVM brings lightning-fast startup times, dramatically reduced memory usage, and smaller container images, making Java applications competitive in cloud-native and serverless environments.


Next Steps

Here are some ways to take this further:

  1. Containerize your native app
    Build a Docker image for your native binary using Quarkus’s preconfigured Dockerfiles:

     
    ./mvnw package -Pnative -Dquarkus.native.container-build=true
    docker build -f src/main/docker/Dockerfile.native -t quarkus-graalvm-app .
    docker run -i --rm -p 8080:8080 quarkus-graalvm-app

     

  2. Deploy to Kubernetes or OpenShift
    Quarkus integrates smoothly with Kubernetes. Use the quarkus-kubernetes extension to generate deployment manifests automatically.

  3. Add persistence and features
    Explore Quarkus extensions such as Hibernate ORM with Panache, RESTEasy Reactive JSON, or SmallRye Health for microservice readiness.

  4. Optimize native builds further
    Experiment with build flags and GraalVM options to optimize binary size and startup even more.

  5. Explore Quarkus Dev Services
    Run databases, Kafka, or other services automatically in dev mode with zero config.

✅ With this foundation, you’re ready to build high-performance, cloud-native Java applications that take full advantage of Quarkus and GraalVM.

You can get the full source code on our GitHub.

That's just the basics. If you need more deep learning about Quarkus, Java, and Microservices, you can take the following cheap course:

Thanks!