Quarkus + Hibernate ORM with Panache
Active-record and repository styles with Panache, Flyway migrations, transactions.
# 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.
Other Java templates
Modern Java Rules
Java 21+ defaults: records, sealed types, pattern matching, virtual threads, var.
Java Testing with JUnit 5
JUnit 5 + AssertJ + Mockito + Testcontainers — opinionated test conventions.
Java Virtual Threads & Concurrency
Project Loom virtual threads, structured concurrency, and modern concurrent patterns.
Java Build (Maven & Gradle)
Build conventions, dependency management, multi-module projects, and CI patterns.
Java Streams & Collections
Streams API, immutable collections, Collectors, and idiomatic data manipulation.