Quarkus offers two primary execution modes: JVM Mode (traditional Java Virtual Machine) and Native Mode (compiled to native code via GraalVM). Understanding the differences is crucial for choosing the right approach for your application.
Overview and Setup
Dependencies Configuration
<!-- pom.xml - Quarkus Dependencies -->
<properties>
<quarkus.platform.version>3.8.0</quarkus.platform.version>
</properties>
<dependencies>
<!-- Core Quarkus -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<!-- Testing -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Sample Application for Comparison
Example 1: REST API with Database Access
// Entity
package com.example.entity;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@Entity
public class Product extends PanacheEntity {
@NotBlank
@Size(min = 3, max = 100)
public String name;
public String description;
public Double price;
public Integer stockQuantity;
// Constructors
public Product() {}
public Product(String name, String description, Double price, Integer stockQuantity) {
this.name = name;
this.description = description;
this.price = price;
this.stockQuantity = stockQuantity;
}
}
// Repository
package com.example.repository;
import com.example.entity.Product;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
@ApplicationScoped
public class ProductRepository implements PanacheRepository<Product> {
public List<Product> findByName(String name) {
return find("name like ?1", "%" + name + "%").list();
}
public List<Product> findExpensiveProducts(Double minPrice) {
return find("price >= ?1", minPrice).list();
}
public long countInStock() {
return find("stockQuantity > 0").count();
}
}
// REST Resource
package com.example.resource;
import com.example.entity.Product;
import com.example.repository.ProductRepository;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
@Path("/products")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ProductResource {
@Inject
ProductRepository productRepository;
@GET
public List<Product> getAllProducts() {
return productRepository.listAll();
}
@GET
@Path("/{id}")
public Product getProduct(@PathParam("id") Long id) {
Product product = productRepository.findById(id);
if (product == null) {
throw new WebApplicationException("Product not found", 404);
}
return product;
}
@POST
@Transactional
public Response createProduct(@Valid Product product) {
productRepository.persist(product);
return Response.status(Response.Status.CREATED).entity(product).build();
}
@PUT
@Path("/{id}")
@Transactional
public Product updateProduct(@PathParam("id") Long id, @Valid Product productUpdate) {
Product product = productRepository.findById(id);
if (product == null) {
throw new WebApplicationException("Product not found", 404);
}
product.name = productUpdate.name;
product.description = productUpdate.description;
product.price = productUpdate.price;
product.stockQuantity = productUpdate.stockQuantity;
return product;
}
@GET
@Path("/search")
public List<Product> searchProducts(@QueryParam("name") String name) {
return productRepository.findByName(name);
}
@GET
@Path("/expensive")
public List<Product> getExpensiveProducts(@QueryParam("minPrice") Double minPrice) {
return productRepository.findExpensiveProducts(minPrice != null ? minPrice : 100.0);
}
}
Example 2: Configuration and Additional Services
// Configuration class
package com.example.config;
import io.smallrye.config.ConfigMapping;
import java.util.Optional;
@ConfigMapping(prefix = "app")
public interface AppConfig {
String name();
Optional<String> description();
CacheConfig cache();
interface CacheConfig {
boolean enabled();
int ttl();
int maxSize();
}
}
// Caching service
package com.example.service;
import com.example.config.AppConfig;
import io.quarkus.cache.CacheResult;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import java.time.LocalDateTime;
@ApplicationScoped
public class ProductService {
@Inject
AppConfig appConfig;
@CacheResult(cacheName = "product-cache")
public String getProductInfoWithCache(Long productId) {
// Simulate expensive operation
simulateProcessing();
return "Product info for ID: " + productId + " at " + LocalDateTime.now();
}
private void simulateProcessing() {
try {
Thread.sleep(100); // Simulate processing time
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public boolean isCacheEnabled() {
return appConfig.cache().enabled();
}
}
// Scheduled task
package com.example.task;
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;
@ApplicationScoped
public class MaintenanceTask {
private static final Logger LOG = Logger.getLogger(MaintenanceTask.class);
@Inject
ProductCleanupService cleanupService;
@Scheduled(every = "1h")
void cleanupOldProducts() {
if (cleanupService.isEnabled()) {
LOG.info("Starting scheduled product cleanup");
int cleaned = cleanupService.cleanupOldProducts();
LOG.infof("Cleaned up %d old products", cleaned);
}
}
@Scheduled(every = "5m")
void healthCheck() {
LOG.debug("Performing health check");
// Health check logic
}
}
Performance Comparison Tests
Example 3: Performance Benchmarking
package com.example.benchmark;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class PerformanceComparisonTest {
private static final int WARMUP_ITERATIONS = 100;
private static final int MEASUREMENT_ITERATIONS = 1000;
@Test
@Order(1)
@EnabledIfSystemProperty(named = "quarkus.package.type", matches = "jar")
public void testJvmModePerformance() {
System.out.println("=== JVM MODE PERFORMANCE TEST ===");
runPerformanceTests();
}
@Test
@Order(2)
@EnabledIfSystemProperty(named = "quarkus.package.type", matches = "native")
public void testNativeModePerformance() {
System.out.println("=== NATIVE MODE PERFORMANCE TEST ===");
runPerformanceTests();
}
private void runPerformanceTests() {
// Warmup phase
for (int i = 0; i < WARMUP_ITERATIONS; i++) {
performGetRequest();
performPostRequest();
}
// GET request performance
long getStartTime = System.currentTimeMillis();
for (int i = 0; i < MEASUREMENT_ITERATIONS; i++) {
performGetRequest();
}
long getEndTime = System.currentTimeMillis();
// POST request performance
long postStartTime = System.currentTimeMillis();
for (int i = 0; i < MEASUREMENT_ITERATIONS; i++) {
performPostRequest();
}
long postEndTime = System.currentTimeMillis();
System.out.printf("GET Requests: %d requests in %d ms (avg: %.2f ms/req)%n",
MEASUREMENT_ITERATIONS,
getEndTime - getStartTime,
(double)(getEndTime - getStartTime) / MEASUREMENT_ITERATIONS);
System.out.printf("POST Requests: %d requests in %d ms (avg: %.2f ms/req)%n",
MEASUREMENT_ITERATIONS,
postEndTime - postStartTime,
(double)(postEndTime - postStartTime) / MEASUREMENT_ITERATIONS);
}
private void performGetRequest() {
given()
.when().get("/products")
.then()
.statusCode(200);
}
private void performPostRequest() {
String productJson = """
{
"name": "Test Product",
"description": "Performance test product",
"price": 99.99,
"stockQuantity": 10
}
""";
given()
.contentType(ContentType.JSON)
.body(productJson)
.when().post("/products")
.then()
.statusCode(201);
}
@Test
public void testMemoryUsage() {
Runtime runtime = Runtime.getRuntime();
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
long maxMemory = runtime.maxMemory();
System.out.printf("Memory Usage: Used=%d MB, Max=%d MB%n",
usedMemory / (1024 * 1024),
maxMemory / (1024 * 1024));
}
@Test
public void testStartupTime() {
// This would typically be measured during application startup
// For demonstration, we'll simulate startup time measurement
long simulatedStartupTime = simulateStartupMeasurement();
System.out.printf("Simulated Startup Time: %d ms%n", simulatedStartupTime);
}
private long simulateStartupMeasurement() {
// In real scenarios, this would measure actual startup time
// For testing purposes, we return a simulated value
String packageType = System.getProperty("quarkus.package.type", "jar");
return "native".equals(packageType) ? 50 : 2000; // Native starts faster
}
}
Build and Deployment Configuration
Example 4: Docker and Build Configuration
# Dockerfile.jvm - For JVM Mode FROM registry.access.redhat.com/ubi8/openjdk-17-runtime:1.18 ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to display heap size ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" COPY target/quarkus-app/lib/ /deployments/lib/ COPY target/quarkus-app/*.jar /deployments/ COPY target/quarkus-app/app/ /deployments/app/ COPY target/quarkus-app/quarkus/ /deployments/quarkus/ EXPOSE 8080 USER 185 ENTRYPOINT [ "java", "-jar", "/deployments/quarkus-run.jar" ]
# Dockerfile.native - For Native Mode FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 WORKDIR /work/ RUN chown 1001 /work \ && chmod "g+rwX" /work \ && chown 1001:root /work COPY --chown=1001:root target/*-runner /work/application EXPOSE 8080 USER 1001 CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
# application.properties - Configuration for both modes # Application config app.name=Product Service app.description=Quarkus Product Management Service app.cache.enabled=true app.cache.ttl=3600 app.cache.max-size=1000 # Database configuration quarkus.datasource.db-kind=postgresql quarkus.datasource.username=quarkus quarkus.datasource.password=quarkus quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/quarkusdb # Hibernate configuration quarkus.hibernate-orm.database.generation=drop-and-create quarkus.hibernate-orm.log.sql=true # HTTP configuration quarkus.http.port=8080 quarkus.http.host=0.0.0.0 # Logging quarkus.log.level=INFO quarkus.log.category."com.example".level=DEBUG # Native-specific configuration quarkus.native.additional-build-args=--initialize-at-build-time=org.slf4j.MDC\ --initialize-at-build-time=org.slf4j.LoggerFactory\ --initialize-at-build-time=org.slf4j.impl.StaticLoggerBinder # JVM-specific configuration quarkus.jvm.initial-ram-percentage=50.0 quarkus.jvm.max-ram-percentage=75.0
Build Commands and Execution
Example 5: Build Scripts and Commands
#!/bin/bash # build-and-compare.sh echo "=== Quarkus JVM vs Native Mode Comparison ===" # Build JVM version echo "Building JVM version..." ./mvnw clean package -DskipTests # Build Native version echo "Building Native version..." ./mvnw clean package -Pnative -DskipTests # Display build sizes JVM_SIZE=$(du -h target/quarkus-app | tail -n1 | cut -f1) NATIVE_SIZE=$(du -h target/*-runner | tail -n1 | cut -f1) echo "=== Build Results ===" echo "JVM Mode size: $JVM_SIZE" echo "Native Mode size: $NATIVE_SIZE" # Startup time comparison echo "" echo "=== Startup Time Comparison ===" echo "Starting JVM application..." time java -jar target/quarkus-app/quarkus-run.jar & JVM_PID=$! sleep 10 # Wait for startup kill $JVM_PID echo "" echo "Starting Native application..." time ./target/*-runner & NATIVE_PID=$! sleep 5 # Native starts faster kill $NATIVE_PID
<!-- pom.xml - Build Profiles --> <profiles> <profile> <id>native</id> <properties> <quarkus.package.type>native</quarkus.package.type> <quarkus.native.container-build>true</quarkus.native.container-build> </properties> <dependencies> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-rest-client-reactive-jackson</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>io.quarkus</groupId> <artifactId>quarkus-maven-plugin</artifactId> <executions> <execution> <goals> <goal>native-image</goal> </goals> <configuration> <enableHttpUrlHandler>true</enableHttpUrlHandler> <enableHttpsUrlHandler>true</enableHttpsUrlHandler> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles>
Comprehensive Comparison Results
Example 6: Comparison Analysis
package com.example.analysis;
import java.util.*;
public class QuarkusComparisonAnalysis {
public static class ComparisonMetrics {
public final String mode;
public final long startupTimeMs;
public final long buildTimeMs;
public final long executableSizeBytes;
public final long memoryUsageMb;
public final double requestsPerSecond;
public final double averageResponseTimeMs;
public ComparisonMetrics(String mode, long startupTimeMs, long buildTimeMs,
long executableSizeBytes, long memoryUsageMb,
double requestsPerSecond, double averageResponseTimeMs) {
this.mode = mode;
this.startupTimeMs = startupTimeMs;
this.buildTimeMs = buildTimeMs;
this.executableSizeBytes = executableSizeBytes;
this.memoryUsageMb = memoryUsageMb;
this.requestsPerSecond = requestsPerSecond;
this.averageResponseTimeMs = averageResponseTimeMs;
}
public void printComparison(ComparisonMetrics other) {
System.out.println("=== QUARKUS " + mode + " vs " + other.mode + " MODE COMPARISON ===");
System.out.printf("Startup Time: %d ms vs %d ms (%.2fx %s)%n",
startupTimeMs, other.startupTimeMs,
(double) other.startupTimeMs / startupTimeMs,
startupTimeMs < other.startupTimeMs ? "faster" : "slower");
System.out.printf("Build Time: %d ms vs %d ms (%.2fx %s)%n",
buildTimeMs, other.buildTimeMs,
(double) other.buildTimeMs / buildTimeMs,
buildTimeMs < other.buildTimeMs ? "faster" : "slower");
System.out.printf("Executable Size: %d MB vs %d MB (%.2fx %s)%n",
executableSizeBytes / (1024 * 1024),
other.executableSizeBytes / (1024 * 1024),
(double) other.executableSizeBytes / executableSizeBytes,
executableSizeBytes < other.executableSizeBytes ? "smaller" : "larger");
System.out.printf("Memory Usage: %d MB vs %d MB (%.2fx %s)%n",
memoryUsageMb, other.memoryUsageMb,
(double) other.memoryUsageMb / memoryUsageMb,
memoryUsageMb < other.memoryUsageMb ? "less" : "more");
System.out.printf("Throughput: %.2f req/s vs %.2f req/s (%.2fx %s)%n",
requestsPerSecond, other.requestsPerSecond,
requestsPerSecond / other.requestsPerSecond,
requestsPerSecond > other.requestsPerSecond ? "higher" : "lower");
System.out.printf("Response Time: %.2f ms vs %.2f ms (%.2fx %s)%n",
averageResponseTimeMs, other.averageResponseTimeMs,
other.averageResponseTimeMs / averageResponseTimeMs,
averageResponseTimeMs < other.averageResponseTimeMs ? "faster" : "slower");
}
}
public static void main(String[] args) {
// Sample metrics (real values would come from actual measurements)
ComparisonMetrics jvmMetrics = new ComparisonMetrics(
"JVM",
2000, // startup time
30000, // build time
150 * 1024 * 1024, // executable size (JAR + JVM)
256, // memory usage
850.5, // requests per second
12.3 // average response time
);
ComparisonMetrics nativeMetrics = new ComparisonMetrics(
"NATIVE",
50, // startup time
120000, // build time
80 * 1024 * 1024, // executable size
45, // memory usage
920.2, // requests per second
10.8 // average response time
);
// Print comparison
nativeMetrics.printComparison(jvmMetrics);
// Print recommendations
printRecommendations(jvmMetrics, nativeMetrics);
}
public static void printRecommendations(ComparisonMetrics jvm, ComparisonMetrics nativeMetrics) {
System.out.println("\n=== RECOMMENDATIONS ===");
if (nativeMetrics.startupTimeMs < jvm.startupTimeMs / 10) {
System.out.println("✅ Use NATIVE mode for serverless/FaaS deployments");
}
if (nativeMetrics.memoryUsageMb < jvm.memoryUsageMb / 2) {
System.out.println("✅ Use NATIVE mode for memory-constrained environments");
}
if (jvm.buildTimeMs < nativeMetrics.buildTimeMs / 3) {
System.out.println("✅ Use JVM mode for development and rapid iteration");
}
if (nativeMetrics.requestsPerSecond > jvm.requestsPerSecond * 1.1) {
System.out.println("✅ Use NATIVE mode for high-throughput applications");
}
if (jvm.averageResponseTimeMs < nativeMetrics.averageResponseTimeMs) {
System.out.println("⚠️ JVM mode may have better peak performance for some workloads");
}
System.out.println("\n=== GENERAL GUIDELINES ===");
System.out.println("• Use JVM mode for: Development, CI/CD, applications with dynamic class loading");
System.out.println("• Use Native mode for: Production, microservices, serverless, resource-constrained environments");
System.out.println("• Consider: Build time, runtime performance, memory usage, and startup time requirements");
}
}
Key Comparison Summary
| Aspect | JVM Mode | Native Mode |
|---|---|---|
| Startup Time | 1-3 seconds | 10-50 milliseconds |
| Memory Usage | 100-500 MB | 20-100 MB |
| Build Time | 30-60 seconds | 2-5 minutes |
| Executable Size | 10-50 MB + JVM | 50-100 MB |
| Peak Throughput | Slightly higher | Slightly lower |
| Development Experience | Excellent | Good (requires rebuild) |
| Debugging | Full debugging support | Limited debugging |
| Reflection | Full support | Requires configuration |
| Dynamic Class Loading | Fully supported | Limited support |
When to Choose Each Mode
Choose JVM Mode When:
- Development phase with frequent code changes
- Complex applications with dynamic class loading
- Applications requiring extensive reflection
- CI/CD pipelines where build time matters
- Applications with complex classpath requirements
Choose Native Mode When:
- Microservices and serverless deployments
- Resource-constrained environments
- Fast startup time is critical (FaaS, containers)
- Reduced memory footprint is important
- Production deployments with stable code
This comprehensive comparison helps developers make informed decisions about when to use Quarkus in JVM mode versus native mode based on their specific requirements and constraints.