quarkus
SKILL.md
Quarkus Guide
Applies to: Quarkus 3.x, Java 17+, Cloud-Native Microservices, GraalVM Native, Kubernetes
Core Principles
- Kubernetes-Native: Designed for containers, fast startup, low memory footprint
- Developer Joy: Live reload via Dev Services, Dev UI, continuous testing
- Unified Reactive/Imperative: Choose per-endpoint, both coexist in one app
- Build-Time Optimization: Extensions process metadata at build time, not runtime
- GraalVM First-Class: Native compilation with millisecond startup and minimal RSS
- Standards-Based: MicroProfile, Jakarta EE, Vert.x, SmallRye under the hood
Guardrails
Project & Dependencies
- Use Quarkus BOM for version management (never pin individual Quarkus artifact versions)
- Add extensions via
./mvnw quarkus:add-extensionrather than editing pom.xml manually - Run
./mvnw quarkus:devfor live reload; never restart manually during development - Use Dev Services (auto-provisioned containers) for databases, Kafka, Redis in dev/test
- Keep
application.propertiesprofile-aware:%dev.,%test.,%prod.prefixes
CDI (Contexts and Dependency Injection)
- Use
@ApplicationScopedfor stateless services (one instance per app) - Use
@RequestScopedonly when you need per-request state - Prefer constructor injection or
@Injectfields (constructor preferred for testability) - Avoid
@Dependentscope unless you need a new instance per injection point - Use
@Startupfor beans that must initialize eagerly at boot - Register beans for native with
@RegisterForReflectiononly when reflection is required - Produce CDI beans via
@Producesmethods for third-party objects
RESTEasy Reactive Resources
- Use RESTEasy Reactive (
quarkus-resteasy-reactive), not classic RESTEasy - Return
Uni<T>orMulti<T>for non-blocking I/O; plain types for blocking is acceptable - Keep resource classes thin: validate input, delegate to service, map response
- Use
@Validon request parameters for Bean Validation - Annotate with
@Produces(APPLICATION_JSON)and@Consumes(APPLICATION_JSON) - Use
@Pathversioning:/api/v1/resource - Add OpenAPI annotations (
@Operation,@APIResponse) on every endpoint - Secure endpoints with
@RolesAllowed,@Authenticated, or@PermitAll
Panache ORM
- Active Record (
extends PanacheEntity): good for simple CRUD entities - Repository (
implements PanacheRepository<T>): good for complex queries, better testability - Use
Uni<T>return types withhibernate-reactive-panachefor non-blocking DB access - Add custom finder methods on entity or repository (e.g.,
findByEmail) - Use
@WithTransactionon service methods that write, not on resources - Set
quarkus.hibernate-orm.database.generation=validateand manage schema via Flyway - Always provide
@PrePersist/@PreUpdatefor audit timestamps
Configuration
- Use
application.propertieswith profile prefixes:%dev.,%test.,%prod. - Inject config values with
@ConfigProperty(name = "key", defaultValue = "fallback") - Group related config with
@ConfigMappinginterfaces - Never hardcode secrets; use
${ENV_VAR:default}placeholder syntax - Enable Dev Services for local containers:
quarkus.devservices.enabled=true
Error Handling
- Create domain exceptions:
ResourceNotFoundException,DuplicateResourceException - Register JAX-RS
@Providerclasses implementingExceptionMapper<T> - Return structured error bodies:
{ title, status, detail, timestamp } - Map
ConstraintViolationExceptionto 400 with per-field error details - Log at WARN for client errors, ERROR for unexpected failures
Native Build Compatibility
- Avoid runtime reflection (use
@RegisterForReflectionwhen unavoidable) - Avoid dynamic proxies and runtime bytecode generation
- Test native build regularly:
./mvnw verify -Pnative - Use
@BuildStepextensions for build-time processing - Prefer compile-time DI and annotation processors (MapStruct, Panache)
- Use
quarkus.native.container-build=trueto build without local GraalVM
Project Structure
myproject/
├── src/main/java/com/example/
│ ├── resource/ # JAX-RS endpoints (thin controllers)
│ │ ├── UserResource.java
│ │ └── AuthResource.java
│ ├── service/ # Business logic
│ │ └── UserService.java
│ ├── repository/ # Panache repositories (optional if using active record)
│ │ └── UserRepository.java
│ ├── entity/ # JPA entities with Panache
│ │ └── User.java
│ ├── dto/ # Request/response records
│ │ ├── UserRequest.java
│ │ └── UserResponse.java
│ ├── mapper/ # MapStruct mappers (compile-time, CDI-scoped)
│ │ └── UserMapper.java
│ ├── exception/ # Domain exceptions + ExceptionMappers
│ │ ├── ResourceNotFoundException.java
│ │ ├── DuplicateResourceException.java
│ │ └── ExceptionMappers.java
│ └── health/ # MicroProfile Health checks
│ └── DatabaseHealthCheck.java
├── src/main/resources/
│ ├── application.properties # Config with profile prefixes
│ └── db/migration/ # Flyway SQL migrations
│ └── V1__create_users.sql
├── src/main/docker/
│ ├── Dockerfile.jvm
│ └── Dockerfile.native
├── src/test/java/com/example/
│ ├── resource/ # @QuarkusTest integration tests
│ │ └── UserResourceTest.java
│ └── service/ # Unit tests with @InjectMock
│ └── UserServiceTest.java
└── pom.xml
resource/-- Thin REST endpoints; input validation and response mapping onlyservice/-- All business logic; transactional boundaries live hererepository/-- Data access (skip if active record pattern suffices)entity/-- JPA entities; keep domain logic minimal, push to servicedto/-- Java records for request/response; use Bean Validation annotationsmapper/-- MapStruct interfaces withcomponentModel = "cdi"exception/-- Domain exceptions and JAX-RS ExceptionMapper providershealth/-- MicroProfile HealthCheck implementations
CDI Pattern
@ApplicationScoped
public class UserService {
@Inject
UserRepository userRepository;
@Inject
UserMapper userMapper;
@WithTransaction
public Uni<UserResponse> createUser(UserRequest request) {
return userRepository.existsByEmail(request.email())
.flatMap(exists -> {
if (exists) {
return Uni.createFrom().failure(
new DuplicateResourceException("User", "email", request.email()));
}
User user = userMapper.toEntity(request);
return userRepository.persist(user).map(userMapper::toResponse);
});
}
public Uni<UserResponse> getUserById(Long id) {
return userRepository.findById(id)
.onItem().ifNull().failWith(
() -> new ResourceNotFoundException("User", "id", id))
.map(userMapper::toResponse);
}
}
RESTEasy Resource Pattern
@Path("/api/v1/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Users", description = "User management APIs")
public class UserResource {
@Inject
UserService userService;
@POST
@Operation(summary = "Create a new user")
@APIResponse(responseCode = "201", description = "User created")
public Uni<Response> createUser(@Valid UserRequest request) {
return userService.createUser(request)
.map(user -> Response
.created(URI.create("/api/v1/users/" + user.id()))
.entity(user).build());
}
@GET
@Path("/{id}")
@RolesAllowed({"USER", "ADMIN"})
@SecurityRequirement(name = "jwt")
@Operation(summary = "Get user by ID")
public Uni<UserResponse> getUserById(@PathParam("id") Long id) {
return userService.getUserById(id);
}
}
Panache Entity Pattern
@Entity
@Table(name = "users")
public class User extends PanacheEntity {
@Column(nullable = false, unique = true)
public String email;
@Column(nullable = false)
public String name;
@Column(name = "created_at", updatable = false)
public LocalDateTime createdAt;
@PrePersist
void onCreate() {
createdAt = LocalDateTime.now();
}
// Panache active record queries
public static Uni<User> findByEmail(String email) {
return find("email", email).firstResult();
}
public static Uni<List<User>> findActive() {
return list("active", true);
}
}
DTO Records with Validation
public record UserRequest(
@NotBlank(message = "Email is required")
@Email(message = "Invalid email format")
String email,
@NotBlank(message = "Password is required")
@Size(min = 8, max = 100, message = "Password must be 8-100 characters")
String password,
@NotBlank(message = "Name is required")
@Size(min = 2, max = 100)
String name
) {}
public record UserResponse(
Long id, String email, String name,
String role, boolean active, LocalDateTime createdAt
) {}
Exception Mapper Pattern
@Provider
public class ResourceNotFoundMapper
implements ExceptionMapper<ResourceNotFoundException> {
@Override
public Response toResponse(ResourceNotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of(
"title", "Resource Not Found",
"status", 404,
"detail", e.getMessage(),
"timestamp", Instant.now().toString()))
.build();
}
}
Configuration
# Application
quarkus.application.name=myproject
quarkus.http.port=8080
# Reactive datasource
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=${DB_USERNAME:postgres}
quarkus.datasource.password=${DB_PASSWORD:postgres}
quarkus.datasource.reactive.url=vertx-reactive:postgresql://localhost:5432/mydb
# JDBC (for Flyway migrations)
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/mydb
# Hibernate
quarkus.hibernate-orm.database.generation=validate
quarkus.hibernate-orm.log.sql=true
# Flyway
quarkus.flyway.migrate-at-start=true
quarkus.flyway.locations=db/migration
# Dev Services
%dev.quarkus.datasource.devservices.enabled=true
%dev.quarkus.datasource.devservices.image-name=postgres:15-alpine
# Logging
quarkus.log.level=INFO
quarkus.log.category."com.example".level=DEBUG
%prod.quarkus.log.console.json=true
# JWT
mp.jwt.verify.publickey.location=publicKey.pem
mp.jwt.verify.issuer=https://example.com
# OpenAPI / Swagger
quarkus.smallrye-openapi.path=/api-docs
quarkus.swagger-ui.always-include=true
# Health
quarkus.smallrye-health.root-path=/health
Testing Overview
Standards
- Use
@QuarkusTestfor integration tests (full CDI + HTTP stack) - Use
@InjectMockto mock CDI beans in integration tests - Use
@TestSecurity(user = "test", roles = "ADMIN")to bypass auth in tests - Use REST-assured for HTTP endpoint assertions
- Use
@QuarkusTestResourcewith Testcontainers for DB integration tests - Test native with
./mvnw verify -Pnative(runs@NativeImageTestclasses) - Use
@DisplayNamefor readable test names describing behavior
Integration Test
@QuarkusTest
@DisplayName("UserResource")
class UserResourceTest {
@Test
@DisplayName("POST /api/v1/users - should create user")
void shouldCreateUser() {
given()
.contentType(ContentType.JSON)
.body(new UserRequest("test@example.com", "Password1!", "Test"))
.when()
.post("/api/v1/users")
.then()
.statusCode(201)
.body("id", notNullValue())
.body("email", equalTo("test@example.com"));
}
@Test
@TestSecurity(user = "admin", roles = "ADMIN")
@DisplayName("GET /api/v1/users - admin can list users")
void adminCanListUsers() {
given()
.when()
.get("/api/v1/users")
.then()
.statusCode(200);
}
}
Commands
# Create project
mvn io.quarkus.platform:quarkus-maven-plugin:3.6.0:create \
-DprojectGroupId=com.example \
-DprojectArtifactId=myproject \
-Dextensions="resteasy-reactive-jackson,hibernate-reactive-panache,reactive-pg-client"
# Dev mode (live reload + Dev Services)
./mvnw quarkus:dev
# Run tests
./mvnw test
# Build JVM jar
./mvnw package
# Build native binary
./mvnw package -Pnative
# Build native in container (no local GraalVM)
./mvnw package -Pnative -Dquarkus.native.container-build=true
# Verify native (integration tests against native binary)
./mvnw verify -Pnative
# List available extensions
./mvnw quarkus:list-extensions
# Add extension
./mvnw quarkus:add-extension -Dextensions="openapi"
# Build container image
./mvnw package -Dquarkus.container-image.build=true
# Deploy to Kubernetes
./mvnw package -Dquarkus.kubernetes.deploy=true
# Lint / format
./mvnw compile # Runs annotation processors
mvn checkstyle:check # If checkstyle configured
Best Practices Summary
Do
- Use Dev Services for automatic local containers
- Use Panache for simpler data access patterns
- Use reactive types (
Uni,Multi) for I/O-bound operations - Use
@WithTransactionon service methods (not resources) - Configure for native compilation early; test regularly
- Use health checks (
@Liveness,@Readiness,@Wellness) - Use profile-based configuration (
%dev.,%test.,%prod.) - Use MapStruct (compile-time) over reflection-based mappers
Do Not
- Mix blocking calls in reactive pipelines (use
@Blockingannotation if needed) - Use runtime reflection without
@RegisterForReflection - Hardcode configuration values (use
@ConfigPropertyor@ConfigMapping) - Use synchronous JDBC APIs with reactive datasources
- Ignore native build compatibility until deployment time
- Put business logic in resource classes
Advanced Topics
For detailed patterns and examples, see:
- references/patterns.md -- Reactive pipelines, Hibernate Reactive, testing strategies, GraalVM native, messaging, security, observability
External References
Weekly Installs
6
Repository
ar4mirez/samuelGitHub Stars
3
First Seen
Mar 1, 2026
Security Audits
Installed on
opencode6
gemini-cli6
github-copilot6
codex6
kimi-cli6
amp6