Hibernate Meets the Apocalypse: Quarkus SQL Imports Made Simple
Build a fun Zombie Survival Registry while mastering Quarkus’s latest database import feature — a must-read for Java developers using Hibernate ORM.
When the undead rise, you’ll need more than courage. You’ll need a database!
Today, we’ll build the Zombie Survival Registry, a Quarkus application that tracks survivors, zombies, and supply caches.
What makes this tutorial special is that we’ll use Quarkus’s new ZIP-file SQL import support, added in PR #47338. It lets you package all your initialization scripts into one compressed archive, which perfect for managing multi-file database setups.
What You’ll Build
A fully functional apocalypse registry with:
Multiple SQL scripts packed in a ZIP file
Hibernate ORM with Panache for simple entity management
REST endpoints to explore survivors, zombies, and supplies
Automatic database initialization from the archive
Prerequisites
Java 21 or newer
Maven 3.9+
Quarkus 3.17+
A sense of humor about the inevitable zombie uprising
Bootstrap Your Apocalypse
Let’s create a Quarkus project. Run:
mvn io.quarkus:quarkus-maven-plugin:create \
-DprojectGroupId=org.survival \
-DprojectArtifactId=zombie-registry \
-DclassName="org.survival.SurvivorResource" \
-Dpath="/survivors" \
-Dextensions="hibernate-orm-panache,jdbc-postgresql,rest-jackson"
cd zombie-registryConfigure Your Database
Open src/main/resources/application.properties and add:
quarkus.datasource.db-kind = postgresql
# Drop and recreate schema on startup
quarkus.hibernate-orm.schema-management.strategy=drop-and-create
# HERE’S THE MAGIC
quarkus.hibernate-orm.sql-load-script=import-scripts.zip
# Show executed SQL statements
quarkus.hibernate-orm.log.sql=trueThis tells Quarkus to unpack import-scripts.zip from your classpath and execute its SQL files alphabetically.
Model the Apocalypse
We’ll need three entities: Survivor, Zombie, and SupplyCache.
Survivor.java
package org.survival;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.*;
import java.time.LocalDate;
import java.util.List;
@Entity
@Table(name = “survivors”)
public class Survivor extends PanacheEntity {
public String name;
@Column(name = “zombie_kills”)
public int zombieKills;
@Enumerated(EnumType.STRING)
public SkillSet skillSet;
@Column(name = “days_survived”)
public int daysSurvived;
@Column(name = “has_been_bitten”)
public boolean hasBeenBitten;
@Column(name = “last_seen_date”)
public LocalDate lastSeenDate;
public enum SkillSet {
MEDIC, ENGINEER, SCAVENGER, WARRIOR, FARMER, SCIENTIST
}
public static List<Survivor> findUnbitten() {
return list(”hasBeenBitten”, false);
}
public static List<Survivor> findTopWarriors(int limit) {
return find(”ORDER BY zombieKills DESC”).page(0, limit).list();
}
}Zombie.java
package org.survival;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@Table(name = “zombies”)
public class Zombie extends PanacheEntity {
public String type;
@Column(name = “speed_level”)
public int speedLevel;
@Column(name = “intelligence_level”)
public int intelligenceLevel;
@Column(name = “last_spotted_zone”)
public String lastSpottedZone;
@Column(name = “threat_level”)
public String threatLevel;
public static long countByThreatLevel(String level) {
return count(”threatLevel”, level);
}
}SupplyCache.java
package org.survival;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@Table(name = “supply_caches”)
public class SupplyCache extends PanacheEntity {
@Column(name = “location_name”)
public String locationName;
public double latitude;
public double longitude;
@Column(name = “food_units”)
public int foodUnits;
@Column(name = “water_units”)
public int waterUnits;
@Column(name = “medical_supplies”)
public int medicalSupplies;
@Column(name = “ammunition_count”)
public int ammunitionCount;
@Column(name = “is_compromised”)
public boolean isCompromised;
}Create Your SQL Scripts
Inside src/main/resources, create a folder:
sql-imports/Add your schema and data scripts, prefixed numerically to enforce order:
001-create-survivors.sql
002-create-zombies.sql
003-create-supply-caches.sql
004-insert-survivors.sql
005-insert-zombies.sql
006-insert-supplies.sqlExample — 004-insert-survivors.sql:
-- The brave souls who’ve made it this far
INSERT INTO survivors (id, name, zombie_kills, skillSet, days_survived, has_been_bitten, last_seen_date) VALUES
(1, ‘Mad Max Miller’, 147, ‘WARRIOR’, 892, false, ‘2025-10-26’),
(2, ‘Dr. Sarah Chen’, 23, ‘MEDIC’, 856, false, ‘2025-10-26’),
(3, ‘Rusty “The Wrench” Rodriguez’, 89, ‘ENGINEER’, 734, false, ‘2025-10-25’),
(4, ‘Silent Sue’, 203, ‘SCAVENGER’, 901, false, ‘2025-10-26’),
(5, ‘Farmer Joe Jackson’, 12, ‘FARMER’, 645, false, ‘2025-10-24’),
(6, ‘Professor Quantum’, 5, ‘SCIENTIST’, 423, true, ‘2025-10-20’),
(7, ‘Rookie Rick’, 0, ‘SCAVENGER’, 2, false, ‘2025-10-26’),
(8, ‘Tank Thompson’, 312, ‘WARRIOR’, 967, false, ‘2025-10-26’),
(9, ‘Nurse Nightingale’, 67, ‘MEDIC’, 789, false, ‘2025-10-26’),
(10, ‘Crazy Eddie’, 445, ‘WARRIOR’, 1001, false, ‘2025-10-26’);Add similar inserts for zombies and supplies.
Each .sql file is plain text . You don’t need special syntax.
Bundle Your Scripts into a ZIP
Quarkus automatically extracts ZIP archives, so just compress your SQLs:
cd src/main/resources/sql-imports
zip -r ../import-scripts.zip *.sql
cd ../../../..Or automate it in your build with:
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>zip-sql-imports</id>
<phase>generate-resources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<zip destfile=”${project.build.outputDirectory}/import-scripts.zip”
basedir=”${project.basedir}/src/main/resources/sql-imports”
includes=”*.sql” />
</target>
</configuration>
</execution>
</executions>
</plugin>
Build the REST Endpoints
SurvivorResource.java
package org.survival;
import java.util.List;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path(”/survivors”)
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class SurvivorResource {
@GET
public List<Survivor> all() {
return Survivor.listAll();
}
@GET
@Path(”/unbitten”)
public List<Survivor> unbitten() {
return Survivor.findUnbitten();
}
@GET
@Path(”/leaderboard”)
public List<Survivor> leaderboard(@QueryParam(”limit”) @DefaultValue(”5”) int limit) {
return Survivor.findTopWarriors(limit);
}
@GET
@Path(”/stats”)
public Response stats() {
long total = Survivor.count();
long bitten = Survivor.count(”hasBeenBitten”, true);
long kills = Survivor.find(”SELECT SUM(s.zombieKills) FROM Survivor s”)
.project(Long.class)
.firstResult();
return Response.ok(new Stats(total, bitten, kills)).build();
}
@POST
@Transactional
public Response add(Survivor survivor) {
survivor.persist();
return Response.status(Response.Status.CREATED).entity(survivor).build();
}
public record Stats(long total, long bitten, long totalZombieKills) {
}
}Add companion resources for /zombies and /supplies when you want. I’ll add them in the project source code. Each uses Panache queries to simplify persistence.
Start Your Database
Launch Quarkus in dev mode. It automatically starts the Postgresql as a dev service.
./mvnw quarkus:devQuarkus will:
Detect
import-scripts.zipExtract it to a temp folder
Execute SQL files in alphabetical order
Populate the database with your data
You’ll see log output confirming script execution.
Verify Your Survival API
Test your endpoints:
# List survivors
curl http://localhost:8080/survivors
# Top warriors
curl http://localhost:8080/survivors/leaderboard?limit=3
# Unbitten only
curl http://localhost:8080/survivors/unbitten
# Survivor stats
curl http://localhost:8080/survivors/stats
# Zombie threats
curl 'http://localhost:8080/zombies/threat/RUN!!!'
# Safe caches
curl http://localhost:8080/supplies/safeExpected output:
Survivors with names and kill counts
JSON statistics like:
{”total”:10,”bitten”:1,”totalZombieKills”:1293}Threat counts for each zombie type
If you see your inserts reflected: Congratulations, your apocalypse is under control.
Why ZIP Import Matters
Before this feature, you needed to juggle multiple import.sql files and environment-specific scripts.
Now you can:
Group many SQLs into one ZIP archive
Maintain a predictable alphabetical execution order
Version-control a single artifact
Automate ZIP creation in CI/CD
It’s cleaner, faster, and production-ready.
Pro Tips for Real Projects
Prefix files numerically — controls execution order
Use idempotent DDL — include
IF NOT EXISTSto avoid re-runs breaking thingsKeep comments in SQL — document what each file does
Use profiles — define
%dev,%test,%prodZIPsAutomate packaging — integrate ZIP creation into Maven or Gradle builds
Example:
%dev.quarkus.hibernate-orm.sql-load-script=import-scripts-dev.zip
%prod.quarkus.hibernate-orm.sql-load-script=import-scripts-prod.zipCongratulations!
You’ve just built a complete Zombie Apocalypse Database powered by Quarkus and Hibernate ORM Panache.
You now know how to:
Package initialization SQLs in a single archive
Configure Quarkus to import from ZIPs
Define simple JPA entities
Expose REST endpoints for survivor stats
If you can survive this tutorial, you can survive anything — including schema migrations.
Stay safe, keep coding, and remember:
the first rule of surviving the apocalypse is backing up your database.



