Quarkus Holiday Wishlist Tutorial: A Festive Guide to Hibernate Soft Deletes
Build a joyful end-to-end Wishlist app with Quarkus, PostgreSQL Dev Services, and Hibernate ORM 7’s soft-delete magic.
Manage gifts. Archive old wishes. Restore magic when needed.
The holiday season is full of lists. Gifts to buy. Wishes to fulfill.
In this tutorial you build a small Quarkus application that manages gift wishlists using Hibernate ORM 7 soft deletes.
A soft delete acts like placing an item in Santa’s “Archive Sack” instead of destroying it forever.
Perfect for:
accidental deletions
later audits
“bring this back next year” scenarios
family members who can’t decide what they want
This example shows you:
how to model a Wishlist with soft deletes
how to expose REST endpoints
how to restore archived wishes
how to permanently delete items (“North Pole Purge Mode”)
how to build a small Qute page to display active vs. archived wishes
Works with Quarkus 3.17+, Java 21, and Podman for Dev Services with PostgreSQL.
Project Setup
Create a new Quarkus app or grab the source code from my Github repository.
quarkus create app com.festive:wishlist-app \
--extension="hibernate-orm-panache,jdbc-postgresql,rest-jackson,rest-qute,qute"Start Dev Mode:
mvn quarkus:devQuarkus starts PostgreSQL automatically through Dev Services.
Database Model: The Wish & The Wishlist
We build:
Wishlist– a festive list owned by a userWish– individual wish inside a wishlist (e.g., “LEGO Millennium Falcon”)Soft-delete enabled on both
Hibernate ORM 7 handles all filtering.
Entity: Wishlist
package com.festive.wishlist;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.annotations.SoftDelete;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
@Entity
@Table(name = “wishlists”)
@SoftDelete // adds deleted BOOLEAN column
public class Wishlist extends PanacheEntity {
public String ownerName;
@OneToMany(mappedBy = “wishlist”, cascade = CascadeType.ALL, orphanRemoval = true)
List<Wish> wishes = new ArrayList<>();
public void addWish(Wish wish) {
wishes.add(wish);
wish.wishlist(this);
}
// Active Record Pattern: static query methods
public static List<Wishlist> findActive() {
return listAll();
}
}Soft delete cascades through wishes if configured.
Entity: Wish (with holiday flair)
Using a custom soft-delete column name: archived.
package com.festive.wishlist;
import java.util.List;
import org.hibernate.annotations.SoftDelete;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
@Entity
@Table(name = “wishes”)
@SoftDelete(columnName = “archived”) // holiday archive sack
public class Wish extends PanacheEntity {
public String description;
public String priority; // “high”, “medium”, “low”
public boolean kidApproved;
@ManyToOne
@JoinColumn(name = “wishlist_id”)
public Wishlist wishlist;
public void wishlist(Wishlist wishlist) {
this.wishlist = wishlist;
}
// Active Record Pattern: find active wishes
public static List<Wish> findActive() {
return listAll();
}
// Active Record Pattern: find archived wishes (bypasses soft delete filter)
public static List<Wish> findArchived() {
// Use native query to bypass soft delete filter and get archived wishes
@SuppressWarnings(”unchecked”)
List<Wish> archived = getEntityManager()
.createNativeQuery(”SELECT * FROM wishes WHERE archived = true”, Wish.class)
.getResultList();
return archived;
}
// Active Record Pattern: restore soft-deleted wish
public static void restore(Long wishId) {
getEntityManager().createNativeQuery(”UPDATE wishes SET archived = false WHERE id = :id”)
.setParameter(”id”, wishId)
.executeUpdate();
}
// Active Record Pattern: hard delete (bypasses soft delete)
public static void hardDelete(Long wishId) {
getEntityManager().createNativeQuery(”DELETE FROM wishes WHERE id = :id”)
.setParameter(”id”, wishId)
.executeUpdate();
}
}REST API: Managing your Holiday Wishes
Create a resource at:
src/main/java/com/festive/wishlist/WishlistResource.java
package com.festive.wishlist;
import java.util.List;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path(”/wishlist”)
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class WishlistResource {
// Create wishlist
@POST
@Transactional
public Wishlist create(Wishlist wishlist) {
if (wishlist == null) {
throw new jakarta.ws.rs.BadRequestException(”Wishlist data is required”);
}
wishlist.persist();
return wishlist;
}
// Add wish
@POST
@Path(”/{id}/wish”)
@Transactional
public Wishlist addWish(@PathParam(”id”) Long id, Wish wish) {
if (wish == null) {
throw new jakarta.ws.rs.BadRequestException(”Wish data is required”);
}
Wishlist list = Wishlist.findById(id);
if (list == null) {
throw new jakarta.ws.rs.NotFoundException(”Wishlist with id “ + id + “ not found”);
}
list.addWish(wish);
wish.persist();
return Wishlist.findById(id);
}
// Get only active wishlist entries
@GET
public List<Wishlist> activeWishlists() {
return Wishlist.findActive();
}
// Soft-delete an entire wishlist
@DELETE
@Path(”/{id}”)
@Transactional
public void delete(@PathParam(”id”) Long id) {
Wishlist list = Wishlist.findById(id);
if (list != null) {
list.delete(); // soft delete
}
}
// Soft-delete a wish
@DELETE
@Path(”/{wishlistId}/wish/{wishId}”)
@Transactional
public void deleteWish(@PathParam(”wishlistId”) Long wishlistId,
@PathParam(”wishId”) Long wishId) {
Wish wish = Wish.findById(wishId);
if (wish == null) {
throw new jakarta.ws.rs.NotFoundException(”Wish with id “ + wishId + “ not found”);
}
wish.delete();
}
// Restore a soft-deleted wish
@PUT
@Path(”/wish/{wishId}/restore”)
@Transactional
public void restoreWish(@PathParam(”wishId”) Long wishId) {
Wish.restore(wishId);
}
// Hard-delete (North Pole Purge Mode)
@DELETE
@Path(”/wish/{wishId}/purge”)
@Transactional
public void hardDeleteWish(@PathParam(”wishId”) Long wishId) {
Wish.hardDelete(wishId);
}
}A Festive Qute Page
Create a simple page showing:
Active wishes
Archived wishes (“The Naughty Bin”)
Create:
src/main/resources/templates/wishes.html
<!DOCTYPE html>
<html>
<head>
<meta charset=”UTF-8”>
<title>Holiday Wishlist</title>
<style>
<!-- omitted -->
</style>
</head>
<body>
<h1>🎄 Holiday Wishlist 🎄</h1>
<h2>Active Wishes</h2>
{#for wish in active}
<div class=”wish-box”>
<strong>{wish.description}</strong>
<em>Priority: {wish.priority}</em>
</div>
{/for}
<h2>Archived Wishes 🎅 (Santa’s Storage)</h2>
{#for wish in archived}
<div class=”wish-box archived”>
<strong>{wish.description}</strong>
<em>Priority: {wish.priority}</em>
</div>
{/for}
</body>
</html>
Qute Resource Endpoint
src/main/java/com/festive/wishlist/WishPage.java
package com.festive.wishlist;
import java.util.List;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path(”/ui”)
public class WishPage {
@Inject
Template wishes; // matches wishes.html
@GET
@Produces(MediaType.TEXT_HTML)
public TemplateInstance show() {
// Active Record Pattern: use static methods on entity
List<Wish> active = Wish.findActive();
List<Wish> archived = Wish.findArchived();
return wishes.data(”active”, active)
.data(”archived”, archived);
}
}Now open:
http://localhost:8080/uiAnd enjoy your festive, soft-delete-powered wishlist.
Testing the Flow
Start Quarkus:
mvn quarkus:devCreate a wishlist:
curl -X POST -H "Content-Type: application/json" \
localhost:8080/wishlist \
-d '{"ownerName":"Svenja"}'Add wishes:
curl -X POST -H "Content-Type: application/json" \
localhost:8080/wishlist/1/wish \
-d '{"description":"LEGO Millennium Falcon","priority”:"high","kidApproved":true}'curl -X POST -H "Content-Type: application/json" \
localhost:8080/wishlist/1/wish \
-d '{"description":"Chocolate Santa","priority":"low","kidApproved":true}'
Soft-delete a wish:
curl -X DELETE localhost:8080/wishlist/1/wish/2Restore a wish:
curl -X PUT localhost:8080/wishlist/wish/2/restoreHard-delete (“North Pole Purge Mode”):
curl -X DELETE localhost:8080/wishlist/wish/2/purgeWhat Hibernate Does Under the Hood
For @SoftDelete(columnName = “archived”), Hibernate creates:
archived BOOLEAN DEFAULT FALSEWhen deleting a Wish:
update wishes set archived=true where id=?Queries automatically include:
where archived=falseIt’s invisible and automatic. Perfect for seasonal data management.
Final Touches: Festive Wrapping Paper
You now have:
A wishlist domain model
Soft delete applied on wishlist & wishes
Automatic filtering of active entries
REST API
Qute festive UI
Restore + hard-delete options
PostgreSQL Dev Services integration
This is a complete, fun, end-to-end holiday example.
If you want more holiday magic, add:
a “Santa Recommendation Engine” using LangChain4j
an “Naughty/Nice Scoring System” using Panache & rules
WebSockets so wishes update live as children change their minds
Happy coding and may your build logs be evergreen. 🎄




