Quarkus
Quarkus rules and best practices for Claude Code.
8.2k
- Quarkus
- Java
- Jakarta EE
- MicroProfile
- GraalVM
- Postgres
- Docker
- Kotlin
6 TEMPLATES
Modern Quarkus Rules
# CLAUDE.md — Modern Quarkus ## Version baseline - Quarkus 3.6+ (3.15+ if you can adopt). Java 21. - Pin Quarkus platform BOM in `pom.xml` / `quarkusPlatformVersion` in Gradle. - Single source of truth for all Quarkus extensions — never mix versions. ## Dev mode - `./mvnw quarkus:dev` (or `./gradlew --console=plain quarkusDev`) is the way you develop. - Live reload on file change. The dev UI runs at `localhost:8080/q/dev-ui`. - **Continuous testing** (press `r` in the dev console) runs tests on every save. - Don't deploy dev mode to production. It's not optimized. ## Build-time vs runtime - Quarkus does most work at **build time**: classpath scanning, reflection registration, dependency wiring. - Application startup is fast because there's nothing to discover at runtime. - Don't try to defer config to runtime if you don't need it. Build-time `@ConfigMapping` is faster and statically typed. ## Configuration - `application.properties` (or `.yml` with the `quarkus-config-yaml` extension) for app config. - `@ConfigProperty(name = "...")` for individual properties. - `@ConfigMapping(prefix = "...")` for grouped, type-safe access: ```java @ConfigMapping(prefix = "app.email") interface EmailConfig { String fromAddress(); @WithDefault("smtp.gmail.com") String smtpHost(); int smtpPort(); } ``` - Profiles: `%dev`, `%test`, `%prod`. Override per profile: `%prod.app.email.smtp-host=...`. ## CDI - Quarkus uses **ArC** — a build-time CDI implementation. Most Jakarta CDI APIs work; some runtime-only patterns don't. - `@ApplicationScoped` for singletons. `@RequestScoped` per HTTP request. `@Dependent` for shared cheap beans. - Constructor injection via `@Inject`. Field injection works but constructor is preferred. - Producers (`@Produces`) for third-party types you can't annotate. ## Logging - Use `org.jboss.logging.Logger` or just SLF4J — both work via the same Quarkus logging. - `quarkus.log.console.json=true` for structured logs in production. - Per-package log levels: `quarkus.log.category."com.app".level=DEBUG`. ## Health - Built-in `/q/health`, `/q/health/live`, `/q/health/ready`. - Auto-detects DB and message-broker health via extensions. Custom checks implement `HealthCheck`. ## Don't - Don't use reflection-heavy frameworks without Quarkus extensions. They won't work in native mode and probably won't optimize well in JVM either. - Don't store mutable state in `@ApplicationScoped` beans without synchronization. - Don't put config in `META-INF/microprofile-config.properties` — use `application.properties` for project consistency. - Don't ignore the dev UI. It surfaces config, beans, endpoints, and metrics for free.Quarkus REST (RESTEasy Reactive)
# CLAUDE.md — Quarkus REST (RESTEasy Reactive) ## Extension choice - Use **`quarkus-rest`** (RESTEasy Reactive). It's the modern default — built-in reactive, faster, smaller native image. - The classic `quarkus-resteasy` (non-reactive) is legacy. Don't start new projects with it. - For a fully reactive stack, pair with `quarkus-rest-jackson` (or `quarkus-rest-jsonb`) for JSON. ## Resource classes - `@Path("/users")` on a class. Methods use JAX-RS annotations: `@GET`, `@POST`, `@Path("/{id}")`. - Don't use `@ApplicationScoped` on resources unless you understand the lifecycle. RESTEasy Reactive handles this for you. - Inject services via constructor (`@Inject` on the constructor params is implicit in Quarkus). ## Reactive vs imperative - Methods can return `T` (blocking, runs on the worker thread) or `Uni<T>` / `Multi<T>` (reactive, runs on the event loop). - For purely reactive workloads, return `Uni<T>`. For mixed code, blocking is fine — Quarkus dispatches automatically. - Annotate with `@RunOnVirtualThread` (Quarkus 3.10+) to opt in to virtual-thread dispatch — best of both worlds. ## Request/response shaping - Records as DTOs. Use `@JsonbProperty` or Jackson's `@JsonProperty` to rename fields when client contracts diverge. - Validate with Bean Validation: `@Valid` on the body parameter, `@NotNull` / `@Email` / `@Size` on fields. - For errors, throw `WebApplicationException` with a status — or define an `ExceptionMapper`. ## Exception mapping - One `ExceptionMapper<X>` per error type. Quarkus auto-discovers them. - Use `RestResponse<T>` to return typed responses with status: ```java RestResponse<UserDto> get(@PathParam("id") String id) { var user = service.find(id); return user.map(RestResponse::ok) .orElseGet(() -> RestResponse.status(404)); } ``` ## Validation errors - `ConstraintViolationException` is auto-mapped to a 400 with field-level errors. Customize via a custom mapper. - For consistent error envelopes, define a `ProblemDetail`-like DTO and use it everywhere. ## OpenAPI - Add `quarkus-smallrye-openapi`. The spec is exposed at `/q/openapi`, Swagger UI at `/q/swagger-ui`. - Annotate with `@Operation`, `@APIResponse` for richer docs. Most generation is automatic from JAX-RS + Bean Validation. ## CORS - Enable globally: `quarkus.http.cors=true`, `quarkus.http.cors.origins=https://app.example.com`. - Don't set `*` with credentials. The browser will refuse. ## Don't - Don't mix RESTEasy Reactive and non-reactive in the same project. - Don't return `Response.ok().entity(x).build()` when `RestResponse.ok(x)` does the same with type safety. - Don't put logic in `ExceptionMapper`. They translate; logic belongs in services. - Don't disable native compilation tests for new endpoints. RESTEasy works in native, but reflection-heavy custom code might not.Quarkus + Hibernate ORM with Panache
# CLAUDE.md — Quarkus + Hibernate ORM with Panache ## Pick a style - **Active record**: entity extends `PanacheEntity` and exposes `find()`, `persist()`, `count()` directly on the class. - **Repository**: separate `PanacheRepository<E>` class; entities stay POJOs. - Pick one per project. Mixing is confusing. - For DDD-style codebases, repositories are usually the right call. For CRUD apps, active record is faster to write. ## Entities - Use records-friendly entity patterns: ```java @Entity public class User extends PanacheEntity { public String email; public String name; public Instant createdAt = Instant.now(); } ``` - Public fields are fine — Panache rewrites them to property accessors at build time. - Use `@Id` + `@GeneratedValue` only if you need a custom ID strategy. Default is `Long id`. ## Queries - Derived queries via static methods on the entity (active record): ```java public static List<User> findByEmail(String email) { return list("email", email); } ``` - Or via repository methods: ```java @ApplicationScoped public class UserRepository implements PanacheRepository<User> { public List<User> findByEmail(String email) { return list("email", email); } } ``` - Use `find(...)` for lazy queries, `list(...)` for eager. `firstResult()` / `singleResult()` to materialize. - Pagination: `find("...").page(Page.of(0, 20)).list()`. ## Transactions - `@Transactional` on the service method. Don't put it on entities or repositories. - Reads don't need a transaction unless they touch lazy collections — use `@Transactional` defensively if you're not sure. - For **reactive Panache**, use `@WithTransaction` on `Uni`-returning methods. ## Migrations - **Flyway** (`quarkus-flyway`) or **Liquibase** (`quarkus-liquibase`). Pick one. - `quarkus.flyway.migrate-at-start=true` for dev. In production, run migrations as a separate step. - Don't rely on `quarkus.hibernate-orm.database.generation=update` outside dev — it's not safe for production. ## DTO projection - For list views, project to a DTO instead of returning entities: ```java public static List<UserListDto> listUsers() { return find("select new com.app.UserListDto(id, email) from User").project(UserListDto.class).list(); } ``` - Avoid loading associations you don't render. ## Reactive variant - Use `quarkus-hibernate-reactive-panache` for non-blocking DB access. - Pair with `vertx-pg-client` driver. SQL drivers are blocking and won't work. - Methods return `Uni<T>` and `Multi<T>`. Lifecycle integrates with RESTEasy Reactive endpoints. ## Don't - Don't use Panache active record for CRUD-heavy domain models with rich behavior. They get crowded fast. - Don't put query logic in resources. Push it to the entity (active record) or repository. - Don't share the same entity class between two databases. Use separate persistence units. - Don't use the blocking and reactive Panache variants in the same project.Quarkus + GraalVM Native
# CLAUDE.md — Quarkus + GraalVM Native ## When to go native - Cold-start matters: serverless (Lambda, Cloud Run cold starts), CLI tools, scale-to-zero deployments. - Memory matters: tight container sizes, edge environments. - **Don't** go native when: - Throughput is the priority (JIT eventually beats native after warm-up) - The team isn't ready for the build complexity - You depend on libraries that need extensive reflection / dynamic class loading without Quarkus extensions ## Build - `./mvnw package -Pnative` (or `./gradlew build -Dquarkus.package.type=native`). - Use **Mandrel** (Red Hat's GraalVM distribution focused on native-image). Pin the version: `quarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:23.1-java21`. - Container build: `quarkus.native.container-build=true` — works without GraalVM installed locally. - Build is slow (1–5 minutes). Optimize CI with cached Maven repo + builder image. ## Reflection & resources - Quarkus extensions register most reflection automatically. - For your own code that uses reflection (rare), register classes: ```java @RegisterForReflection(targets = { com.app.SomeDto.class }) public class ReflectionConfig {} ``` - Resources accessed at runtime: `quarkus.native.resources.includes=path/to/file.json,**/*.properties`. - Static initializers that touch the environment (file system, network) need `--initialize-at-run-time` hints — usually handled by Quarkus. ## Runtime gotchas - Native binaries don't support agents. JFR works but with limitations. - Reflection on classes not registered fails at runtime — catch in tests, not prod. - Class-loading is closed-world. No `Class.forName(dynamicString)` patterns. ## Testing - `@QuarkusIntegrationTest` runs against the packaged artifact (jar or native). - Use `mvn verify -Pnative` to run integration tests against the native binary in CI. - Tag native-only tests `@DisabledOnIntegrationTest` if they only make sense in JVM mode. ## Image size - Mandrel base image is a few hundred MB. Final native binary ~50–100 MB. - Strip the image with `quarkus.native.compression.level=10` for binary compression. - Use `distroless/static` or `scratch` as the runtime image — copy in the binary, run it. ## Memory - Native uses much less heap than JVM. Set `-Xmx256m` and watch — many apps run on 64–128 MB. - The savings come from no JIT, no class metadata, no interpreter overhead. ## Don't - Don't switch to native to "optimize" without measuring. JVM with Quarkus already starts in 1–2s and uses ~150 MB. - Don't expect native + reflection-heavy library to "just work". Test the integration in native mode early. - Don't skip running the native test suite in CI. The cost is real but cheaper than a prod surprise. - Don't use `--no-fallback` until you're sure your app works fully native — the fallback image is a JVM bundled into the binary, not what you want.GraalVM + Quarkus Native
# CLAUDE.md — GraalVM + Quarkus Native ## Why Quarkus + native is a sweet spot - Quarkus is **designed** for native compilation. Most extensions ship native hints out of the box. - Build time is fast (relative to other Java frameworks) thanks to Quarkus' build-time framework processing. - Cold-start on Lambda / Cloud Run drops from 5+ seconds (JVM Spring) to ~50 ms (native Quarkus). ## Mandrel vs upstream GraalVM - **Mandrel** is Red Hat's downstream of GraalVM CE — focused exclusively on `native-image`. - Quarkus tests against Mandrel by default. Pin a specific Mandrel image: ``` quarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:23.1-java21 ``` - Use upstream GraalVM if you need polyglot, Truffle, or Oracle's advanced JIT optimizations alongside. ## Build commands - JVM mode: `./mvnw package` (still produces an optimized AOT artifact in Boot/Quarkus). - Native mode: `./mvnw package -Pnative`. - Container build (no GraalVM/Mandrel locally): `./mvnw package -Pnative -Dquarkus.native.container-build=true`. ## Configuration knobs - `quarkus.native.additional-build-args` for niche flags. - `quarkus.native.resources.includes` for resources you load via `getResource`. - `quarkus.native.compression.level=10` (Quarkus 3.5+) for smaller binaries (UPX-based). - Disable monitoring extras you don't need: `--no-fallback`, `-H:-AddAllCharsets`. ## Reflection - Most Quarkus extensions register reflection automatically via the build-time SPI. - For custom code: `@RegisterForReflection(targets = MyDto.class)`. - For JSON DTOs (Jackson, JSON-B): annotation is needed if the class is loaded reflectively. Records and standard JAX-RS DTOs usually work without it. ## Image size - Typical Quarkus native image: 50–80 MB binary, ~100 MB Docker image with distroless. - Strip what you don't use: - `quarkus.native.enable-https-url-handler=false` if not needed - `quarkus.native.enable-jni=false` if no JNI deps - `quarkus.native.add-all-charsets=false` (default) — explicit charset list via `quarkus.native.include-charsets=UTF_8` ## Tests - `@QuarkusIntegrationTest` runs against the packaged native binary. Add to CI; don't push without it. - Use the same test classes for JVM and native — Quarkus picks the artifact based on the Maven profile. - Slow native build means CI matrix: native tests on main; JVM-mode on PRs. ## Common pitfalls - A library that uses `Class.forName(stringFromConfig)` at runtime — won't work in native unless every possible class is in the reachability metadata. - Static initializers that touch the network or filesystem — they run at build time by default and can leak build-host state into the binary. - JNI without explicit configuration — needs `jni-config.json` and the library binary in the image. ## Workflow - Develop in JVM dev mode (`./mvnw quarkus:dev`). Live reload is invaluable. - Run native tests on PR merges, not every push. Build is too slow for inner loop. - Profile native binaries with `--enable-monitoring=jfr` and Java Flight Recorder. ## Don't - Don't build native locally on every save. Use JVM dev mode. - Don't skip `@QuarkusIntegrationTest` for native. Bugs that hide in JVM emerge in native. - Don't pull in Spring/Quarkus mixed-stack libraries — Quarkus' native story works because every extension is on board. - Don't deploy native binaries built on Mac for Linux containers without cross-compilation. Use the container builder image.Quarkus Testing
# CLAUDE.md — Quarkus Testing ## Test types - `@QuarkusTest` — full Quarkus app loaded. Use for integration-style tests of endpoints, services, and DB. - `@QuarkusIntegrationTest` — runs against the packaged artifact (JVM jar or native binary). Use for smoke tests of the deployed bundle. - Plain JUnit 5 — for pure logic that doesn't need Quarkus. ## Dev services - Quarkus auto-starts containers (Postgres, Redis, Kafka, etc.) for tests when the right extensions are on the classpath. - No `@Container`, no Testcontainers boilerplate — just add the extension and write the test. - Override with explicit config when you need a specific image or version. ## REST testing - Use **REST-assured** for endpoint tests: ```java given().contentType(JSON).body(req) .when().post("/users") .then().statusCode(201).body("email", is("a@b")); ``` - `@TestHTTPEndpoint(UsersResource.class)` to skip repeating the path prefix. - For reactive endpoints, REST-assured works the same — the reactive nature is server-side. ## Mocking - `@InjectMock` to replace a CDI bean with a Mockito mock for one test class: ```java @QuarkusTest class UserServiceTest { @Inject UserService service; @InjectMock EmailGateway email; } ``` - For full bean replacement (alternative implementation), use `@Alternative` + `@Priority`. - Don't `@InjectMock` value types — mock services and gateways at the bean boundary. ## Test profiles - `@TestProfile(MyProfile.class)` to override config per test class: ```java static class MyProfile implements QuarkusTestProfile { @Override public Map<String, String> getConfigOverrides() { return Map.of("app.email.smtp-host", "test.example.com"); } } ``` - A new app is started per profile. Group tests with the same profile to keep startup costs amortized. ## Database - Each test gets a transaction that rolls back via `@TestTransaction`: ```java @Test @TestTransaction void persists_user() { repository.persist(new User(...)); } ``` - For tests that need committed state (multi-thread, async), don't use `@TestTransaction` — clean up explicitly. ## Speed - Quarkus reuses the application context across `@QuarkusTest` classes when configs match. Don't fragment with unique profiles unless necessary. - Run unit tests first, integration tests later in CI. Fail fast. - Continuous testing in dev mode (`r` in the dev console) re-runs only affected tests. ## Don't - Don't use Spring's `@SpringBootTest` mental model in Quarkus. Quarkus tests are usually faster and need less setup. - Don't `@InjectMock` infrastructure beans Quarkus controls (e.g., `EntityManager`). Use `@TestTransaction` and a real DB. - Don't run native tests on every save. Save them for `mvn verify -Pnative` in CI. - Don't share state across tests via static fields. Tests run in undefined order.
Have a CLAUDE.md template that works for you?
Send it in — we’ll credit you and publish it under the right tags.