Optimizing Quarkus Performance: Startup Time, Memory & Native Builds
Quarkus has quickly become one of the most popular JVM frameworks for building cloud-native, container-friendly applications. Its core philosophy- “Supersonic Subatomic Java” -comes from two key performance goals:
- Blazing-fast startup time, ideal for serverless, FaaS, and container scaling.
- Low memory footprint, making it highly efficient on Kubernetes, microservices, and edge environments.
Unlike traditional Java frameworks that rely heavily on runtime classpath scanning, reflection, and dynamic configuration, Quarkus pushes as much work as possible to build time. This build-time optimizations model makes Quarkus extremely fast in both JVM mode and, especially, when compiled to a native executable using GraalVM or Mandrel.
However, like any framework, Quarkus applications don’t automatically reach peak performance without the right configuration. Startup time, live memory consumption, and overall runtime efficiency can vary depending on:
What You Will Learn
In this tutorial, we will walk through the most practical techniques to optimize Quarkus performance. You will learn how to:
- Measure baseline performance (startup time, memory usage, throughput)
- Improve startup time in JVM mode
- Reduce RAM usage in both JVM and native builds
- Create optimized native executables with GraalVM or Mandrel
- Tune your application.properties for maximum efficiency
- Optimize database connections and caching
- Profile and monitor your application with JDK Flight Recorder and Micrometer
- Apply real-world improvements to a sample Quarkus REST API
By the end, you’ll be able to build highly efficient, production-ready Quarkus applications that scale faster, consume fewer resources, and perform better under load.
To begin optimizing Quarkus, we need a baseline project to measure and improve. In this section, we’ll create a simple REST API using the Quarkus CLI, add the essential extensions, and prepare our development environment for JVM and native builds.
1. Install Quarkus CLI (If Not Installed)
If you don’t have the Quarkus CLI, install it using SDKMAN (recommended):
sdk install quarkusOr upgrade to the latest:
sdk upgrade quarkusYou can verify the installation with:
quarkus --versionThe CLI helps generate projects, add extensions, build native images, and run dev mode with improved speed.
2. Create a New Quarkus Project
Use the Quarkus CLI to scaffold a project with RESTEasy Reactive:
quarkus create app com.djamware:quarkus-performance:1.0.0 \ --extensions="resteasy-reactive,resteasy-reactive-jackson"This generates the following structure:
quarkus-performance/ ├── src/ ├── pom.xml ├── README.md └── src/main/java/com/djamwareWhy RESTEasy Reactive?
3. Add Optional Extensions for This Tutorial
We will need additional extensions later in the tutorial for performance testing, caching, and metrics.
Add them now:
cd quarkus-performance quarkus extension add \ quarkus-smallrye-health \ quarkus-micrometer-registry-prometheus \ quarkus-hibernate-orm-panache \ quarkus-jdbc-h2 \ quarkus-cacheExtensions Breakdown
4. Create a Simple REST Endpoint
Let’s add a basic API we’ll use as our baseline.
Create:src/main/java/com/djamware/resource/HelloResource.java
package com.djamware.resource; 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 HelloResource { @GET @Produces(MediaType.APPLICATION_JSON) public String hello() { return "{\"message\":\"Hello from Quarkus!\"}"; } }Start the application in dev mode:
quarkus devThen open:
http://localhost:8080/helloIf you see the JSON response, your baseline API is ready.
5. JVM vs Native Profiles
Quarkus has three important modes:
We’ll use all three throughout this tutorial.
6. Prepare Application Configuration
Open src/main/resources/application.properties and add:
quarkus.http.port=8080 quarkus.datasource.db-kind=h2 quarkus.datasource.jdbc.url=jdbc:h2:mem:testdb quarkus.datasource.jdbc.max-size=8 quarkus.datasource.jdbc.min-size=2 # Enable metrics quarkus.micrometer.export.prometheus.enabled=true # Hibernate optimization quarkus.hibernate-orm.sql-load-script=no-fileThis gives us a reasonable baseline for startup time and memory measurements.
Your project is now fully set up.
Measuring Baseline Performance
Before we optimize anything, we need to understand how the application behaves as-is. Establishing a baseline allows us to quantify improvements in startup time, memory consumption, and runtime performance later in the tutorial.
In this section, you’ll learn how to measure:
1. Measure JVM Startup Time
Build the application in JVM mode:
./mvnw clean packageThen run the JAR manually:
java -jar target/quarkus-app/quarkus-run.jarWhen Quarkus starts, you’ll see logs like:
INFO ... started in 0.997s. Listening on: http://0.0.0.0:8080Record this value-it is your baseline startup time in JVM mode.
2. Measure Memory Usage (JVM)
With the app still running, open another terminal and run:
ps -o pid,rss,command | grep quarkus-runExample output:
49791 146176 java -jar target/quarkus-app/quarkus-run.jar 50281 1296 grep quarkus-runHere:
Alternatively, you can watch usage over time:
or use JDK tools:
jcmd <PID> VM.native_memory summaryJVM memory usage depends on your machine, Java version, and OS. Your numbers may vary slightly.
3. Measure Endpoint Performance
We will test the /hello endpoint using curl and wrk (or hey).
Quick test with curl
curl -w "Total: %{time_total}s\n" http://localhost:8080/helloThroughput test with wrk
If you have wrk installed:
wrk -t4 -c50 -d10s http://localhost:8080/helloExample output:
Requests/sec: 19000 Latency: 2.1msOr with hey (Go-based HTTP load tester):
hey -n 5000 -c 50 http://localhost:8080/helloRecord:
These will be useful later.
4. Accessing Metrics (Micrometer + Prometheus)
Since we installed Micrometer, you can check runtime metrics at:
http://localhost:8080/q/metricsUseful metrics include:
These metrics help validate the accuracy of your manual measurements.
5. Summary: Baseline Results Table
Create a table to capture your baseline. Example:
Your exact numbers may differ, but the goal is to establish your own baseline.
We will revisit these metrics after:
So you can clearly see the performance gains.
Reduce Startup Time (JVM Mode)
Quarkus is already fast by default, but there are several proven techniques to reduce startup time even further. Most improvements come from reducing reflection, trimming unused extensions, and shifting more work to build-time initialization.
This section covers the most impactful optimizations you can apply in JVM mode before moving to native builds.
1. Remove Unused Extensions
Extensions directly impact startup time-each extension adds additional initialization steps.
List installed extensions:
quarkus extension listOr list only those in your project:
quarkus infoIf you see any extensions not needed for your API, remove them:
quarkus extension remove quarkus-xyzFewer extensions = less startup overhead.
2. Use RESTEasy Reactive Instead of Classic
If you installed RESTEasy Classic previously, replace it with RESTEasy Reactive (which we already did in Project Setup).
It provides:
To confirm you’re using Reactive:
quarkus infoLook for:
quarkus-resteasy-reactiveIf “resteasy” (classic) appears, remove it.
3. Disable Unused Features in application.properties
Disable automatic features you are not using:
quarkus.banner.enabled=false quarkus.hibernate-orm.sql-load-script=no-file quarkus.arc.remove-unused-beans=true quarkus.jackson.fail-on-unknown-properties=falseWhat these do:
4. Move Initialization to Build-Time (Avoid Reflection)
Quarkus encourages build-time initialization.
Use constructor injection instead of field injection
Bad (requires reflection):
@Inject MyService service;Good (no reflection):
@ApplicationScoped public class HelloService { private final Repository repo; public HelloService(Repository repo) { this.repo = repo; } }Benefits:
✔ Faster startup
✔ Fewer reflective lookups
✔ Better native build compatibility
5. Reduce Classpath Scanning
Set the following:
quarkus.arc.detect-unused-beans=allThis aggressively removes unused CDI beans.
You can also restrict JPA entity scanning:
quarkus.hibernate-orm.packages=com.djamware.entity6. Enable Classpath Cache (Big Win)
Quarkus can cache the resolved classpath for faster builds and dev mode startup:
quarkus.bootstrap.classpath-cache=trueThe cache is stored in .quarkus/ and reused automatically.
Benefits:
7. Use Fast JAR Packaging
Ensure your JAR is built in “fast-jar” mode:
quarkus.package.jar.type=fast-jarThis is the default, but adding it explicitly helps ensure optimal startup.
8. Use Continuous Testing to Reduce Cold Starts
Instead of repeatedly running fresh builds for tests:
quarkus testOr start dev mode and press:
Continuous testing avoids reboots and shortens the developer cycle startup time.
9. Example: Before vs After Startup Time
Here’s an example of typical improvements from the settings in this section:
On a typical machine, these optimizations reduce startup time by 25–40%.
Your numbers may differ, but similar gains are common.
Your Quarkus application is now much faster in JVM mode!
Improve Memory Consumption
Quarkus significantly reduces memory usage compared to traditional Java frameworks by pushing work to build time, minimizing reflection, and optimizing CDI. However, there are still several ways to further decrease heap and native memory usage — especially important for Kubernetes, serverless, small containers, and cost-efficient deployments.
This section covers the most effective techniques to reduce memory consumption in JVM mode and prepare for even bigger savings in native mode.
1. Use Properly Sized Database Connection Pools
Database connection pools are one of the biggest sources of memory use in microservices.
Default pool sizes are often larger than needed.
Reduce them in application.properties:
quarkus.datasource.jdbc.max-size=8 quarkus.datasource.jdbc.min-size=2 quarkus.datasource.jdbc.initial-size=2For small services, you can go even lower:
quarkus.datasource.jdbc.max-size=4Why this matters:
2. Prefer Reactive SQL Clients (If Applicable)
If your service doesn’t require Hibernate ORM or JDBC, use the reactive client:
quarkus extension add quarkus-reactive-pg-clientBenefits:
✔ Lower memory usage
✔ No heavyweight JDBC driver
✔ Non-blocking I/O reduces thread usage
3. Reduce Hibernate ORM Memory Usage
If you use JPA, apply these settings:
# Disable 2nd-level cache unless needed quarkus.hibernate-orm.second-level-caching-enabled=false # Avoid unnecessary metadata generation quarkus.hibernate-orm.sql-load-script=no-file # Disable statistics quarkus.hibernate-orm.statistics=falseIf your app uses only a few entities, explicitly define scan packages:
quarkus.hibernate-orm.packages=com.djamware.entityThis prevents scanning the entire classpath.
4. Disable Unused CDI Beans
Quarkus aggressively removes unused CDI beans, but you can push further:
quarkus.arc.remove-unused-beans=true quarkus.arc.detect-unused-beans=allResults:
5. Use Caching for Expensive Calls
The Quarkus Cache extension reduces CPU and memory churn by memoizing results.
Example:
@CacheResult(cacheName = "hello-cache") public String getHello() { return "Hello " + System.currentTimeMillis(); }Configure cache size:
quarkus.cache.caffeine.hello-cache.initial-capacity=16 quarkus.cache.caffeine.hello-cache.maximum-size=100Benefits:
6. Tune JVM for Container Environments
When running in Docker or Kubernetes, the JVM needs explicit memory boundaries.
Enable container support automatically:
# JVM respects container memory limits -XX:+UseContainerSupport(This is enabled by default in modern Java versions.)
Set realistic heap sizes:
java -Xms128m -Xmx256m -jar quarkus-run.jarBenefits:
7. Use G1 or Shenandoah GC (Java 17/21+)
G1 is the default and works well for most Quarkus apps, but Shenandoah offers:
Enable Shenandoah:
java -XX:+UseShenandoahGC -jar quarkus-run.jar8. Minimize Thread Usage
Switch to Vert.x / Reactive extensions where possible.
Benefits:
Example:
quarkus extension add quarkus-reactive-routes9. Example: Memory Usage Before vs After Optimization
Example result on a typical Linux machine:
Many real-world Quarkus apps comfortably run at 60–100 MB in JVM mode with proper configuration.
Your application now consumes significantly less memory in JVM mode and is ready for even more dramatic optimization using native builds.
Native Builds with GraalVM
One of Quarkus’s most powerful features is its ability to compile applications into native executables using GraalVM or Mandrel. Native builds offer:
In this section, you’ll install GraalVM (or Mandrel), build a native executable, measure performance gains, and fix common native build issues.
1. Understanding Native Builds
When you build a native image:
- Code is ahead-of-time (AOT) compiled
- Reflection, proxies, and dynamic class loading require configuration
- Startup time improves drastically (often 10–100x faster)
- Memory usage drops by up to 75%
Typical results:
You’ll measure your actual numbers shortly.
2. Install GraalVM or Mandrel
Option A: Install GraalVM (JDK 21 recommended)
Using SDKMAN:
sdk install java 21-graal sdk use java 21-graalVerify:
java -versionOption B: Install Mandrel (Optimized for Quarkus)
Download Mandrel from the official releases or use SDKMAN:
sdk install java 21-mandrel sdk use java 21-mandrel3. Install Native Image Tool
With GraalVM:
gu install native-imageCheck status:
native-image --version4. Build a Native Executable
Inside your project:
./mvnw clean package -PnativeQuarkus creates the native executable in:
target/quarkus-performance-1.0.0-runnerIf you’re using Gradle:
./gradlew build -Dquarkus.package.type=native5. Run the Native Executable
Start the binary:
./target/quarkus-performance-1.0.0-runnerYou should see startup logs like:
INFO ... started in 0.015s. Listening on: http://0.0.0.0:8080That’s 15 milliseconds — over 50× faster than typical JVM mode.
6. Measure Memory Usage (Native)
Open a second terminal and run:
ps -o pid,rss,command | grep quarkus-performanceExample output:
51234 26000 ./quarkus-performance-1.0.0-runnerThis means the app uses:
7. Test Throughput
Even though native mode excels at startup and memory usage, throughput is often comparable to JVM mode.
Using wrk:
wrk -t4 -c50 -d10s http://localhost:8080/helloOr with hey:
hey -n 5000 -c 50 http://localhost:8080/helloDepending on your CPU, throughput may be:
Quarkus native performance varies based on workload type.
8. Common Native Build Issues & Fixes
Native builds require explicit configuration for:
Below are common fixes.
8.1 Reflection Issues
If you see:
ClassNotFoundException: ... at runtime (reflection)Add reflection config:
src/main/resources/reflection-config.json
[ { "name": "com.djamware.entity.User", "allDeclaredFields": true, "allDeclaredConstructors": true, "allDeclaredMethods": true } ]Tell Quarkus to include it:
quarkus.native.additional-build-args=--reflection-configuration-files=reflection-config.json8.2 Missing Resources
If you load resources:
src/main/resources/myfile.txtAdd:
quarkus.native.resources.includes=*.txt8.3 SSL Issues
Enable proper handling:
quarkus.native.enable-all-security-services=true8.4 Jackson Serialization Issues
Enable full Jackson support:
quarkus.native.enable-https-url-handler=true quarkus.jackson.fail-on-unknown-properties=false9. Faster Native Builds with Docker (Recommended)
You can build native images without installing GraalVM locally.
Quarkus provides a containerized builder:
./mvnw package -Pnative -Dquarkus.native.container-build=trueUses:
Good for CI/CD pipelines.
10. Native Build Result Summary
Typical results comparing JVM vs Native:
Native builds shine in:
Your application is now fully optimized for native mode performance.
Quarkus Build Optimization Tips
Quarkus delivers excellent performance out of the box, but you can push it even further with a few strategic build optimizations. These techniques reduce build time, minimize application size, shorten startup time, and improve overall runtime efficiency for both JVM and native builds.
This section covers the most effective build-time optimizations you should consider for production.
1. Enable Classpath Caching
Quarkus can cache classpath resolution across builds, which significantly improves:
Get Didin Jamaludin’s stories in your inbox
Join Medium for free to get updates from this writer.
Add this to application.properties:
quarkus.bootstrap.classpath-cache=trueOnce enabled, Quarkus stores its cache in:
You’ll see immediate improvements when restarting quarkus dev.
2. Use Fast JAR Packaging (Default)
Fast JAR packaging splits your app into:
Ensure you’re using it:
quarkus.package.jar.type=fast-jarBenefits:
3. Remove Dead Code at Build Time
Quarkus uses aggressive dead-code elimination through Arc.
Enable it:
quarkus.arc.remove-unused-beans=true quarkus.arc.detect-unused-beans=all4. Remove Unused Extensions (Large Impact)
To reduce build complexity:
List extensions:
quarkus infoRemove what you don’t need:
quarkus extension remove quarkus-smallrye-openapi quarkus extension remove quarkus-security quarkus extension remove quarkus-hibernate-validatorEvery extension removed:
✔ shrinks application size
✔ shortens build time
✔ reduces startup time
5. Use Dependency Management Strategically
Audit your dependencies:
- Remove unused
testlibraries - Remove unused JDBC drivers
- Replace heavier libs with lightweight ones
A leaner classpath = faster native builds.
6. Optimize Application Configuration for Build-Time Processing
Quarkus favors build-time configuration.
Move as many properties as possible to application.properties rather than environment variables, including:
The more Quarkus knows at build time, the fewer runtime optimizations it must do.
7. Use Container-Based Native Builds for Predictability
Native builds vary by OS. To ensure consistency:
./mvnw package -Pnative -Dquarkus.native.container-build=trueBenefits:
8. Multi-Stage Docker Builds for Lightweight Images
The recommended Dockerfile:
FROM quay.io/quarkus/ubi-quarkus-native-image:latest AS build COPY . /usr/src/app/ RUN mvn -f /usr/src/app/pom.xml -Pnative -Dquarkus.native.container-build=true package FROM quay.io/quarkus/quarkus-micro-image:2.0 COPY --from=build /usr/src/app/target/*-runner /application CMD ["./application"]This produces:
9. Reduce Reflection With Proper Coding Style
Reflection slows down both JVM and native builds.
Avoid:
@Inject MyService service;Prefer:
MyService service; public MyResource(MyService service) { this.service = service; }Constructor injection results in:
10. Use Build-Time Initialized Beans
Mark heavy components as build-time initialized if possible:
quarkus.native.additional-build-args=--initialize-at-build-time=com.djamware.serviceThis reduces:
11. Reduce Log Output for Faster Startup
Logging slows startup during early initialization.
Tune it:
quarkus.log.level=INFO quarkus.log.console.level=INFO quarkus.log.category."io.quarkus".level=WARNINGOr for benchmarks:
quarkus.log.level=ERROR12. Example: Impact of Build Optimizations
Here’s a typical before/after comparison:
These improvements compound with the startup and memory optimizations previously covered.
Your Quarkus application is now leaner, builds faster, and produces smaller artifacts-all of which improve operational speed and efficiency.
Profiling & Monitoring Your App
Optimizing Quarkus performance doesn’t end with configuration tweaks-real improvements come from measuring and observing your application under real workloads. Profiling and monitoring help you:
- Detect slow endpoints
- Observe CPU and memory hotspots
- Find memory leaks
- Visualize thread usage
- Track GC behavior
- Measure request throughput and latency
- Identify performance regressions early
This section covers the essential tools and approaches for profiling and monitoring both JVM and native Quarkus applications.
1. Use Micrometer + Prometheus Metrics
Since we enabled Micrometer earlier, Quarkus automatically exposes metrics at:
http://localhost:8080/q/metricsThis includes:
JVM Metrics
HTTP Metrics
Custom Metrics (Optional)
You can add your own:
@Inject MeterRegistry registry; @PostConstruct void init() { registry.counter("custom_user_hits").increment(); }2. Set Up Prometheus for Scraping
Add Prometheus scrape config:
prometheus.yml
scrape_configs: - job_name: 'quarkus' static_configs: - targets: ['host.docker.internal:8080']Run:
prometheus --config.file=prometheus.ymlNow open Prometheus UI:
http://localhost:9090Try queries like:
3. Visualizing Metrics with Grafana (Optional)
Grafana dashboards are perfect for operational insights.
Import popular dashboards:
Sample charts include:
4. Using JDK Flight Recorder (JFR)
JFR is the most powerful profiling tool for JVM mode.
Start your app with JFR enabled:
java -XX:StartFlightRecording=filename=recording.jfr -jar quarkus-run.jarAfter running load tests, stop the recording:
jcmd <PID> JFR.stop name=1Open the .jfr file with:
What to analyze:
This helps identify slow services or high memory churn.
5. Profiling Native Images
Native binaries can also be profiled-using tools such as:
perf (Linux)
perf record -g ./target/app-runner perf reportasync-profiler
Start:
./profiler.sh -d 30 -f profile.html <PID>Produces a flame graph ( profile.html) showing:
Valgrind / Massif
Memory-focused profiling:
valgrind --tool=massif ./app-runnerOutputs memory usage snapshots.
6. Using VisualVM (JVM Mode Only)
Start VisualVM:
Attach to your Quarkus JVM process.
You can monitor:
Memory leak detection:
7. Enable Quarkus Built-In Trace Logging (For Troubleshooting)
quarkus.log.category."io.quarkus".level=TRACE quarkus.log.level=INFOUseful for diagnosing:
Remember to switch back to INFO in production.
8. Profiling Web Endpoints with Quarkus Dev UI
Run dev mode:
quarkus devOpen:
http://localhost:8080/q/dev/From here you can analyze:
This is extremely useful for early debugging.
9. Load Testing to Produce Real Profiles
Combine tools:
wrk
wrk -t4 -c50 -d20s http://localhost:8080/hellohey
hey -n 10000 -c 100 http://localhost:8080/helloab
ab -n 5000 -c 50 http://localhost:8080/helloRun these while JFR or VisualVM is attached for realistic profiling under load.
10. What to Look For in Profiling Results
If CPU is high:
If memory usage is high:
If throughput is low:
Profiling and monitoring close the loop on performance optimization. With these tools, you can detect issues early, validate improvements, and ensure your Quarkus application stays highly efficient in production.
Real-World Example: Optimizing an API
To make all the concepts in this tutorial practical, we’ll walk through a real-world optimization scenario. We’ll build a simple User API using Quarkus + Hibernate Panache, measure its baseline performance, apply tuning techniques, and measure the performance improvements.
This section demonstrates how Quarkus performance tuning works in a real application-not just theoretical examples.
1. Create the User Entity
Create a simple Panache entity.
src/main/java/com/djamware/entity/User.java
package com.djamware.entity; import io.quarkus.hibernate.orm.panache.PanacheEntity; import jakarta.persistence.Entity; @Entity public class User extends PanacheEntity { public String name; public String email; }This gives us:
2. Add Initial Sample Data
For testing performance, add sample users.
Update application.properties:
quarkus.hibernate-orm.sql-load-script=import.sqlCreate src/main/resources/import.sql:
INSERT INTO user (id, name, email) VALUES (1, 'Alice', '[email protected]'); INSERT INTO user (id, name, email) VALUES (2, 'Bob', '[email protected]');3. Create the UserResource REST API
src/main/java/com/djamware/resource/UserResource.java
package com.djamware.resource; import java.util.List; import com.djamware.entity.User; import jakarta.transaction.Transactional; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; @Path("/users") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class UserResource { @GET public List<User> listUsers() { return User.listAll(); } @POST @Transactional public User create(User user) { user.persist(); return user; } @GET @Path("/{id}") public User get(@PathParam("id") Long id) { return User.findById(id); } }Test in dev mode:
quarkus devEndpoints:
4. Baseline Performance Measurements
With JVM mode:
./mvnw package java -jar target/quarkus-app/quarkus-run.jarMeasure:
Startup Time
Example:
started in 1.15sMemory Usage
Example baseline:
Endpoint Throughput
Using wrk:
wrk -t4 -c50 -d20s http://localhost:8080/usersExample baseline:
5. Apply Optimizations Step-by-Step
Now we apply the tuning techniques from earlier sections.
Step 1: Reduce DB Connection Pool
quarkus.datasource.jdbc.min-size=1 quarkus.datasource.jdbc.max-size=4Step 2: Disable Unused ORM Features
quarkus.hibernate-orm.sql-load-script=import.sql quarkus.hibernate-orm.second-level-caching-enabled=false quarkus.hibernate-orm.statistics=falseStep 3: Enable Unused Bean Removal
quarkus.arc.remove-unused-beans=true quarkus.arc.detect-unused-beans=allStep 4: Enable Build-Time Optimization
quarkus.bootstrap.classpath-cache=trueStep 5: Migrate to Constructor Injection
Update UserResource (optional but recommended):
public UserResource() { }(Not much to inject for now, but useful in larger services.)
Step 6: Switch Logging Level
quarkus.log.level=INFO quarkus.log.category."io.quarkus".level=WARNINGStep 7: Build a Native Executable (Optional)
./mvnw package -PnativeStartup in native mode:
./target/quarkus-performance-1.0.0-runner6. Performance After Optimization
JVM Mode Measurements
After applying optimizations:
Major improvements:
Native Mode Measurements
If using native builds:
Native builds:
7. Summary of Real-World Optimization
This practical example demonstrates how small configuration changes-connection pool size, ORM tuning, unused bean removal, logging tweaks, and classpath caching-can produce big gains in performance.
Key Takeaways:
Your real-world Quarkus API is now significantly faster, lighter, and more efficient-validated by actual benchmarks rather than assumptions.
Deployment Best Practices
Once your Quarkus application is optimized for startup time, memory usage, and native execution, the next step is deploying it efficiently. Quarkus is designed for cloud-native environments and works especially well in containers and Kubernetes. This section covers best practices for packaging, resource sizing, scaling, and production configurations.
1. Use Multi-Stage Docker Builds
To produce the smallest and fastest containers, always build using Quarkus’s recommended multi-stage Docker approach.
Dockerfile (JVM Mode)
FROM maven:3.9.6-eclipse-temurin-21 AS build WORKDIR /usr/src/app COPY . . RUN mvn package -DskipTests FROM eclipse-temurin:21-jre WORKDIR /app COPY --from=build /usr/src/app/target/quarkus-app/ ./ CMD ["java", "-jar", "quarkus-run.jar"]Dockerfile (Native Mode)
FROM quay.io/quarkus/ubi-quarkus-native-image:latest AS build COPY . /usr/src/app/ RUN mvn -f /usr/src/app/pom.xml -Pnative -Dquarkus.native.container-build=true package FROM quay.io/quarkus/quarkus-micro-image:2.0 WORKDIR /work/ COPY --from=build /usr/src/app/target/*-runner /work/application CMD ["./application"]Benefits:
2. Use Distroless Images in Production (Optional)
Distroless images remove unnecessary tools from the container, reducing the attack surface.
Example base image:
FROM gcr.io/distroless/baseUse only for final runtime images- not for builds.
3. Configure Memory Limits Explicitly
When running in Docker or Kubernetes, always define memory limits.
Example Docker run:
docker run -m 256m --memory-swap 256m quarkus-appExample Kubernetes manifest:
resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "500m"Why this matters:
4. Optimize JVM Settings for Containers
Add JVM flags (for JVM mode apps):
java -XX:+UseContainerSupport \ -Xms64m \ -Xmx128m \ -XX:MaxMetaspaceSize=128m \ -jar quarkus-run.jarOr via JAVA_OPTS:
export JAVA_OPTS="-Xms64m -Xmx128m"Recommended for Quarkus JVM builds:
5. Enable Liveness & Readiness Probes
Quarkus integrates with Kubernetes health endpoints out of the box.
Add the health extension:
quarkus extension add quarkus-smallrye-healthEndpoints:
Kubernetes example:
livenessProbe: httpGet: path: /q/health/live port: 8080 readinessProbe: httpGet: path: /q/health/ready port: 8080Probes ensure:
6. Enable Metrics Scraping with Prometheus
If Prometheus is used, expose metrics:
quarkus.micrometer.export.prometheus.enabled=trueKubernetes ServiceMonitor example:
endpoints: - port: http path: /q/metricsThis enables autoscaling based on:
7. Autoscaling Quarkus in Kubernetes (HPA + KEDA)
Horizontal Pod Autoscaler (CPU-based)
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: quarkus-api spec: minReplicas: 1 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70KEDA Scalers (Event-Driven Autoscaling)
KEDA supports:
Perfect for event-driven microservices where the load fluctuates.
8. Use Native Mode When Scaling Matters
Native binaries excel in:
Benefits:
9. Use ConfigMaps and Secrets Correctly
Store environment-specific config externally:
Kubernetes ConfigMap
apiVersion: v1 kind: ConfigMap metadata: name: quarkus-config data: APPLICATION_ENV: productionSecret
apiVersion: v1 kind: Secret metadata: name: db-secret stringData: username: admin password: secretUse them in Quarkus via:
quarkus.datasource.username=${DB_USERNAME} quarkus.datasource.password=${DB_PASSWORD}10. Log Aggregation for Production
Use structured JSON logs:
quarkus.log.console.json=trueTools:
This supports:
11. Secure Your Quarkus Deployment
Best practices:
quarkus.dev-ui.enabled=false12. Example: Production-Ready Resource Settings
Sample full Kubernetes Deployment:
apiVersion: apps/v1 kind: Deployment metadata: name: quarkus-api spec: replicas: 2 selector: matchLabels: app: quarkus-api template: metadata: labels: app: quarkus-api spec: containers: - name: quarkus-api image: djamware/quarkus-api:1.0.0 resources: requests: cpu: "150m" memory: "128Mi" limits: cpu: "500m" memory: "256Mi" ports: - containerPort: 8080 envFrom: - configMapRef: name: quarkus-config - secretRef: name: db-secret livenessProbe: httpGet: path: /q/health/live port: 8080 readinessProbe: httpGet: path: /q/health/ready port: 808013. Summary
A well-deployed Quarkus application should:
- Use optimized container images
- Respect resource limits
- Provide health checks and metrics
- Support autoscaling
- Store config securely
- Run with minimal overhead
Following these deployment practices ensures you get the maximum performance, scalability, and reliability from your Quarkus application in production.
Quarkus is designed with performance at its core, but the real power comes when you combine its built-in optimizations with the tuning techniques we explored in this tutorial. By understanding how Quarkus handles build-time processing, runtime initialization, resource management, and native compilation, you can build applications that are incredibly fast, lightweight, and scalable.
In this guide, you learned how to:
- Set up a baseline Quarkus project
- Measure startup time, memory usage, and throughput
- Reduce startup time through smarter configuration, dependency trimming, and build-time optimizations
- Reduce memory consumption by tuning database pools, disabling unused ORM features, and optimizing CDI
- Build ultra-fast native images with GraalVM or Mandrel
- Profile and monitor your application using JFR, VisualVM, Prometheus, and Micrometer
- Apply optimizations to a real-world User API and see measurable improvements
- Deploy Quarkus applications efficiently using Docker, Kubernetes, resource limits, autoscaling, and native builds
With these practices, you can confidently build Quarkus applications that start in milliseconds, run on minimal memory, and scale instantly — whether you’re deploying to Kubernetes clusters, serverless platforms, or edge devices.
Performance optimization is an ongoing process, so keep profiling, testing, and iterating. As your application grows, the techniques in this tutorial will help you maintain exceptional efficiency and reliability in production.
You can find the full source code on our GitHub.
That’s just the basics. If you need more deep learning about Quarkus and Microservices, you can take the following cheap course:
Thanks!
Originally published at https://www.djamware.com.









