Use Quarkus Panache Repository for native-safe database access
Add Panache Repository support to a Quarkus service and verify CRUD behavior in JVM and native modes.
18 Jan 2026, 13:41 UTC

Problem and takeaway
Quarkus services often begin with plain JPA entities and hand-written DAOs. As the entity count grows, the DAO layer becomes repetitive save, findById, and delete code. Quarkus Panache removes that boilerplate by attaching CRUD operations to entities or repository interfaces. The practical decision is which Panache style to use and how to prove it works before shipping a native image.
Takeaway: use the Panache Repository pattern when you want standard JPA entities without making them extend a Quarkus base class, and verify the setup in both dev mode and a native build before promoting it.
Desired outcome
A Quarkus service that exposes product data through a Panache repository, starts in dev mode with live reload, and produces a native binary that can read and write the database without reflection errors.
Prerequisites
- A Maven or Gradle project using Quarkus 3.x, which pairs with Hibernate ORM 6.x and Panache 2.x.
- The Quarkus Maven plugin or Gradle plugin configured.
- A relational database reachable from the application, or the H2 development database included with Quarkus.
- For native builds: GraalVM installed locally, or Docker available for container-based native compilation.
1. Add the Panache dependency
Run this from the project root. No special operating-system permissions are required, but the build needs network access to download artifacts.
./mvnw quarkus:add-extension -Dextensions="hibernate-orm-panache,postgresql-jdbc"
Replace postgresql-jdbc with your driver if you use another database. The command modifies pom.xml. Expected check: the build downloads the extension and pom.xml contains the Panache artifact. Risk: adding the dependency changes the dependency tree; confirm existing Hibernate event listeners still load because Panache compile-time enhancement can conflict with custom @PostLoad callbacks.
2. Define the entity
Panache works with standard JPA entities. In the Repository pattern, the entity does not need to extend a Panache base class.
package com.example.inventory;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import java.util.UUID;
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
public UUID id;
public String sku;
public Integer stockOnHand;
}
An @Entity is a class mapped to a database table by Hibernate ORM. GenerationType.UUID lets Hibernate generate identifiers.
3. Create the repository interface
Create a CDI-scoped interface that extends PanacheRepository. This gives you listAll, findById, persist, delete, and related methods without writing implementations.
package com.example.inventory;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class ProductRepository implements PanacheRepository {
public Product findBySku(String sku) {
return find("sku", sku).firstResult();
}
}
PanacheRepository is the interface that injects Panache CRUD operations for a given entity type. CDI makes the repository injectable into resources and services.
4. Configure the datasource
Edit src/main/resources/application.properties. This example uses H2 for local verification.
quarkus.datasource.db-kind=h2
quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:inventory
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
drop-and-create rebuilds the schema on every start. Use it only in development; switch to validate or none in production.
5. Verify in Quarkus dev mode
Run from the project root:
./mvnw quarkus:dev
Expected checks:
- The console shows the H2 schema being created and the Panache repository bean starting.
- A request to your endpoint returns product rows without a 500 error.
- Editing the entity or repository and saving triggers automatic reload without restarting the JVM.
Risk: if you added custom Hibernate listeners, watch for startup warnings about enhancer conflicts.
6. Build and run the native image
Native compilation requires ahead-of-time knowledge of which classes and fields need reflection. Quarkus registers many Panache elements automatically, but custom access patterns can still fail at runtime.
./mvnw package -Pnative -Dquarkus.native.container-build=true
Where to run: project root. Permissions: Docker group membership if using container builds; otherwise GraalVM on PATH. Expected checks:
- The build finishes and produces
target/*-runner. - Running the binary starts the HTTP server.
- A request to the product endpoint returns data and does not throw
NoSuchMethodExceptionor reflection warnings related toProductfields.
Risk: native builds are slow and can fail if Panache-enhanced fields are not whitelisted. If the binary starts but fails when accessing Product.sku, register the entity for reflection:
@RegisterForReflection
public class Product { ... }
Alternatively, supply a reflection configuration file to the native build. Rebuild after any change.
Limitations
- Panache v2.x with Hibernate ORM 6.x changed some lazy-loading defaults. Explicitly declare fetch strategies or use
findwith joins instead of assuming lazy collections load outside a transaction. - The Repository pattern keeps entities free of Quarkus base classes, but you lose the convenience of calling
Product.findAll()directly on the entity. - Native images increase build time and require reflection verification for any custom query logic that touches fields reflectively.
Recovery options
Because this procedure changes project state, you can revert it:
- Remove the Panache extension from
pom.xmlorbuild.gradle. - Delete or replace the repository and entity classes.
- Restore
application.propertiesto the previous datasource configuration. - Run
./mvnw cleanto remove generated native artifacts.
Check the result
Confirm success with this combination:
- Dev mode starts and
curl http://localhost:8080/productsreturns JSON. - Native binary starts, binds port 8080, and the same request returns data.
- No reflection warnings appear in the native startup log for
ProductorProductRepository.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.