Introduction
In 2026, Quarkus dominates the Java framework landscape for microservices thanks to its GraalVM native compilation, slashing startup times to under 10 ms and memory usage by 80% compared to Spring Boot. Ideal for Kubernetes and serverless, it delivers supersonic subatomic Java: lightning-fast builds, hot reload in dev mode, and modular extensions for Kafka, gRPC, or OpenTelemetry.
This expert tutorial walks you step-by-step through building a complete microservice: reactive REST API, persistence with Hibernate Reactive, unit tests, native builds, and Kubernetes deployment. Every line of code is production-ready and copy-pasteable. Picture a framework that compiles your app into a standalone binary like Go or Rust—but in pure Java. That's Quarkus. By the end, you'll command advanced configs for production scaling. (142 words)
Prerequisites
- JDK 17+ (GraalVM 22+ recommended for native)
- Maven 3.9+ or Gradle 8+
- Docker and Kubernetes (minikube for testing)
- Advanced knowledge: Java 21 records/sealed classes, reactivity (Mutiny), Hibernate ORM
- IDE: IntelliJ with Quarkus Tools or VS Code
Create the Quarkus Project
mkdir quarkus-microservice && cd quarkus-microservice
quarkus create app --extensions=resteasy-reactive,hibernate-reactive-panache,quarkus-smallrye-health,quarkus-container-image-docker,quarkus-kubernetes \
com.example:quarkus-microservice:1.0.0-SNAPSHOT
# Alternative Maven CLI si quarkus CLI non installé
mvn io.quarkus.platform:quarkus-maven-plugin:3.15.1:create \
-DprojectGroupId=com.example -DprojectArtifactId=quarkus-microservice \
-Dextensions="resteasy-reactive,hibernate-reactive-panache,quarkus-smallrye-health,quarkus-container-image-docker,quarkus-kubernetes" \
-DnoCodeThis command initializes a project with reactive REST, reactive PostgreSQL persistence (auto-provisioned via Dev Services), health checks, native Docker builds, and Kubernetes manifests. The --noCode flag skips boilerplate so you can customize everything. Tip: Install the quarkus CLI via brew or SDKMAN for ease.
Generated Project Structure
The skeleton includes src/main/java for JAX-RS resources and Panache entities, application.properties for configs, and pom.xml with extensions. Dev Services auto-starts Postgres in dev without manual setup—pure Quarkus magic. Hot reload with ./mvnw quarkus:dev: edit code, auto-refresh in <1s, like Vite for Java.
Define the Entity and Repository
package com.example;
import io.quarkus.hibernate.reactive.panache.PanacheEntityBase;
import io.smallrye.mutiny.Uni;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
public class Product extends PanacheEntityBase {
@Id @GeneratedValue
public Long id;
public String name;
public Double price;
public static Uni<Product> findByName(String name) {
return find("name", name).firstResult();
}
public static Uni<Long> deleteByName(String name) {
return delete("name", name);
}
}Extends PanacheEntityBase for reactive CRUD with Mutiny (Uni/Multi). Built-in static repository avoids DAO boilerplate. Pitfall: Always wrap in Uni for non-blocking; @GeneratedValue auto-increments IDs.
Implement the REST Resource
package com.example;
import io.smallrye.mutiny.Uni;
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 {
@GET
public Uni<List<Product>> getAll() {
return Product.listAll();
}
@GET
@Path("{id}")
public Uni<Response> getById(@PathParam("id") Long id) {
return Product.findById(id)
.onItem().transform(product -> product != null ? Response.ok(product) : Response.status(404, "Not found"))
.onFailure().transform(e -> Response.status(500, e.getMessage()));
}
@POST
public Uni<Response> create(Product product) {
return product.persist().map(ignore -> Response.created(null).build());
}
@DELETE
@Path("{id}")
public Uni<Response> delete(@PathParam("id") Long id) {
return Product.deleteById(id).map(deleted -> Response.noContent().build());
}
}Reactive JAX-RS resource: Uni for async handling. Fluid Mutiny chains manage 404/500 errors. @PathParam for dynamic routes. Pro advantage: Zero XML, auto CDI injection, horizontal scaling without blocking.
Database Configuration
Dev Services provisions in-memory Postgres. Override in prod with env vars. Health checks exposed at /q/health for Kubernetes readiness probes.
Advanced Configuration File
quarkus.datasource.db-kind=postgresql
quarkus.datasource.reactive.url=postgresql://localhost:5432/microservice
quarkus.datasource.username=quarkus
quarkus.datasource.password=quarkus
quarkus.hibernate-reactive.database.generation=drop-and-create
quarkus.log.level=INFO
%prod.quarkus.container-image.build=true
%prod.quarkus.container-image.group=learni
%prod.quarkus.kubernetes.deploy=true
quarkus.smallrye-health.root-path=/q/health
# Native hints pour GraalVM
quarkus.native.additional-build-args=--report-unsupported-elements-at-runtimeProfiled configs with %prod for build/deploy. drop-and-create in dev, validate in prod. Native args avoid GraalVM warnings. Pitfall: Don't forget quarkus.container-image.build=true for auto Docker.
Reactive Unit Tests
package com.example;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;
@QuarkusTest
public class ProductResourceTest {
@Test
public void testCreateAndGet() {
Product product = new Product();
product.name = "Laptop";
product.price = 999.99;
given()
.body(product)
.contentType(ContentType.JSON)
.when().post("/products")
.then()
.statusCode(201);
given()
.when().get("/products")
.then()
.statusCode(200)
.body("[0].name", is("Laptop"));
}
}@QuarkusTest mocks DB with H2, injects CDI. RestAssured for HTTP assertions. Covers create/list; add @TestHTTPEndpoint for integration. Expert tip: Tests run 10x faster in parallel than JVM mode.
Native Build and Docker
./mvnw clean package -Pnative -Dquarkus.native.container-build=true
# Image auto-générée : quay.io/learni/quarkus-microservice:1.0.0-SNAPSHOT-native
# Test local
docker run -i --rm -p 8080:8080 quay.io/learni/quarkus-microservice:1.0.0-SNAPSHOT-native
# Push et deploy K8s
./mvnw deploy -Dquarkus.kubernetes.deploy-mode=openshift # ou kubernetesnative profile compiles to <50MB binary, 0.01s startup. Container build in-Docker for M1/ARM. deploy generates Deployment/Service YAML. Pitfall: First GraalVM build takes 20-30min; cache layers.
Best Practices
- Strict Profiling: Use
%dev,%test,%prodfor secrets/env vars; never hardcode. - Mutiny Everywhere: Avoid blocking
await(); chainonFailure().recoverWith(). - Lazy Extensions:
quarkus-arcfor DI,smallrye-opentracingfor auto-tracing. - Native-Ready:
@RegisterForReflectionon dynamic classes, test native units. - Observability: Enable
/q/metrics, Prometheus scrape for K8s HPA.
Common Errors to Avoid
- IO Blocking: Use
Uni.createFrom().emitter()instead ofThread.sleep(). - GraalVM Reflection: Missing
@NativeImageHintcrashes runtime; usenative-image-agent. - Dev Services Leaks: Always
./mvnw cleanafter dev, orquarkus.devservices.enabled=false. - K8s Scaling: Missing
/q/health/readyreadiness probe = pods pending forever.
Next Steps
Dive into Quarkus Kafka Reactive or gRPC. Master GraalVM with our expert training: Learni Java Cloud-Native Training. Official docs: guide.quarkus.io.