Quarkus Panache: One‑Line CRUD with Hibernate ORM
Learn how Quarkus Panache lets you write CRUD operations in a single line, see a minimal working example, and understand its limits and common pitfalls.
14 Mar 2026, 07:40 UTC

What you can do with one line of Panache code
Quarkus Panache turns a plain JPA entity into a fully‑featured CRUD service. With only @Entity and a single inheritance, you get an id field, persist(), delete(), and static finder methods like findAll() or findById(id). In a REST endpoint you can write:
@POST
public void add(@Valid Person person) {
person.persist();
}
@GET
public List<Person> list() {
return Person.findAll().list();
}
That’s it: no EntityManager, no JPQL, no transaction boilerplate (except the @Transactional annotation on write methods). The framework wires everything for you.
How the mechanism works
Panache is a thin layer over Hibernate ORM. When you extend PanacheEntity or PanacheEntityBase, Quarkus generates a static EntityManager proxy and injects it into the base class. The persist() method simply calls entityManager.persist(this) and commits the transaction if the method is annotated with @Transactional. Static finder methods use the same proxy to execute typed queries.
For reactive applications, replace PanacheEntity with PanacheEntityBase and use Mutiny’s Uni return types:
@GET
public Uni<List<Person>> list() {
return Person.findAll().list();
}
Here findAll() returns a PanacheQuery that can be turned into a non‑blocking stream.
Minimal working example
1. Create a Maven project with the Quarkus extensions you need:
mvn io.quarkus:quarkus-maven-plugin:create \
-DprojectGroupId=com.example \
-DprojectArtifactId=panache-demo \
-Dextensions="quarkus-resteasy-jsonb,quarkus-hibernate-orm-panache"
2. Configure the datasource in src/main/resources/application.properties:
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=${DB_USER}
quarkus.datasource.password=${DB_PASS}
quarkus.datasource.jdbc.url=jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}
quarkus.hibernate-orm.database.generation=drop-and-create
3. Define the entity:
package com.example;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;
@Entity
public class Person extends PanacheEntity {
public String name;
}
4. Add a REST resource:
package com.example;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import java.util.List;
@Path("/person")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class PersonResource {
@POST
@Transactional
public void create(Person person) {
person.persist();
}
@GET
public List<Person> list() {
return Person.findAll().list();
}
}
5. Run the application:
./mvnw quarkus:dev
Send a POST to http://localhost:8080/person with JSON {"name":"Alice"} and GET /person to see the record returned.
Practical limits
- Complex queries: Panache static methods generate simple JPQL. For JOINs, sub‑queries, or advanced criteria you still need
PanacheRepositoryor rawEntityManagercode. - Large result sets:
list()loads all rows into memory. Use pagination (page(),limit()) or the reactive stream API for big data. - Transaction control: Write operations must be annotated with
@Transactionalwhen usingPanacheRepository. Forgetting it results in aTransactionRequiredException. - Schema generation:
drop-and-createis handy for dev but wipe data on every restart. In production switch tovalidateornone. - Reactive vs blocking: Mixing Mutiny (
Uni) with blocking code can exhaust the event loop. Keep the same programming model per endpoint. - Inheritance mapping: Using
PanacheEntityin an inheritance hierarchy requires proper JPA annotations (@Inheritance,@DiscriminatorColumn) to avoid mapping errors.
Common mistakes to avoid
- Missing
@Entityannotation – the class won’t be registered with Hibernate. - Extending
PanacheEntityBasewithout specifying theidtype – the base class expects a primary key type. - Not marking service methods with
@Transactional– data will not be flushed. - Expecting lazy loading in reactive endpoints – reactive Panache disables lazy fetching; use
eagerorjoin fetchif needed. - Using
findAll().list()on a huge table – leads to OutOfMemoryError; use pagination. - Running with
drop-and-createin production – data loss.
Verification checklist
- Run
./mvnw quarkus:devand confirm the application starts without errors. - POST a JSON payload to
/personand verify the HTTP status is 204 (no content). - GET
/personand confirm the returned JSON array contains the posted record. - Check the logs for a transaction commit message (e.g.,
Hibernate: commit). - For reactive tests, replace the entity with
PanacheEntityBase, useUni, and ensure the endpoint returns without blocking the event loop. Verify by inspecting the thread pool metrics in/q/metrics.
When all checks pass, your CRUD API is ready and you’ve verified that Panache’s automatic persistence works as expected.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.