This is a cache of https://developer.ibm.com/tutorials/quarkus-basics-03/. It is a snapshot of the page as it appeared on 2025-12-05T02:55:31.888+0000.
Mastering data persistence with Quarkus - IBM Developer

Tutorial

Mastering data persistence with Quarkus

Discover the power of Quarkus and Dev Services for streamlined data persistence

In this tutorial, you will connect your Quarkus application to a database. You will use Hibernate ORM with Panache, which gives you a clean API for working with relational data. Quarkus will start run PostgreSQL automatically through Dev Services so that you don’t need to install the database manually and for when you run your app in dev or test mode.

Prerequisites

Before you begin, make sure that you have installed:

Step 1. Installing a container runtime for Dev Services

Behind the scenes, Dev Services uses Podman or Docker. These tools run lightweight containers that hold services like PostgreSQL. If you don’t already have Podman or Docker installed, Dev Services will not work, because Quarkus needs a container runtime to launch the database.

Installing Podman

For Windows, follow the official instructions to download the Podman Windows installer.

For Linux based systems, pick your favorite packaging manager or install the binary for your platform directly from the Podman Github repository.

You can use sudo or Homebrew to install Podman:

  • macOS (with Homebrew): brew install podman
  • Fedora, RHEL, or CentOS: sudo dnf install podman
  • Debian or Ubuntu: sudo apt-get install podman

Then, verify your installation:

podman --version

Installing Docker

You can also use Docker. Follow the installation instructions on Docker.com.

Step 2. Add Panache and PostgreSQL extensions

You’ve already seen that Quarkus uses extensions to add capabilities. To work with relational data, you need Hibernate ORM with Panache (to simplify database access) and PostgreSQL (the database driver).

Let’s create a new project that scaffolds the with the beforementioned extensions:

quarkus create app com.ibm.developer:module-three \
  --extension=quarkus-rest-jackson,quarkus-hibernate-orm-panache,quarkus-jdbc-postgresql

This creates a new project called “module-three” with the right extensions:

  • quarkus-rest-jackson - REST API with JSON
  • quarkus-hibernate-orm-panache - JPA entities with Panache
  • postgresql - JDBC driver

Notice that you are not limited to PostgreSQL. Quarkus and Hibernate support various databases. Check out more details in the official Quarkus guide for Hibernate ORM.

When you now run Quarkus in dev mode, Quarkus will notice the PostgreSQL dependency and automatically start a PostgreSQL container with Dev Services.

Step 3. Create an entity class

Almost every application needs to store data in a database. In Java, this is usually done through the Java Persistence API (JPA). JPA is a standard that defines how Java objects can be mapped to database tables. On its own, JPA is just an API. It needs an implementation to do the real work.

One of the most popular implementations is Hibernate ORM. Hibernate takes care of translating between Java objects and database rows. It is powerful, but comes with powers that can make it complex.

Quarkus builds on top of Hibernate ORM and provides Panache, a simplified persistence layer. Panache reduces the amount of code that you need to write, so working with entities feels natural and lightweight.

Let’s modify and rename the scaffolded MyEntity.java to src/main/java/com/ibm/developer/Fruit.java:

package com.ibm.developer;

import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;

@Entity
public class Fruit extends PanacheEntity {
    public String name;
    public String color;
}

Here’s what happens in this class:

  • @Entity marks the class as a JPA entity, meaning it maps to a database table.
  • By extending PanacheEntity, you automatically get an ID column and built-in methods like persist(), findAll(), and deleteById().
  • Each field (name, color) becomes a column in the Fruit table.

This way, you can create, find, and delete objects in the database with very little code.

Step 4. Expose data through REST

Having an entity class is not enough. You need a way to access it from outside your application. REST endpoints provide this access. By exposing your entity through REST, you allow other applications or frontend code to interact with your data. You already know how to build them with Quarkus REST.

When you save, update, or delete data in a database, these actions need to happen inside a transaction. A transaction is like a safety wrapper: either all changes succeed, or none of them are applied. This ensures that your data stays consistent, even if something goes wrong.

In Quarkus, you mark methods that change data with the annotation @Transactional. This tells Quarkus and Hibernate to start and commit a transaction automatically around your method. Without it, your changes would not be written to the database.

Rename and modify the scaffolded GreetingResource to src/main/java/com/ibm/developer/FruitResource.java:

package com.ibm.developer;

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.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/fruits")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class FruitResource {

    @GET
    public List<Fruit> list() {
        return Fruit.listAll();
    }

    @POST
    @Transactional
    public Fruit add(Fruit fruit) {
        fruit.persist();
        return fruit;
    }

    @DELETE
    @Path("/{id}")
    @Transactional
    public void delete(@PathParam("id") Long id) {
        Fruit.deleteById(id);
    }
}

Now you have three REST operations:

  • GET /fruits lists all fruits
  • POST /fruits adds a new fruit (inside a transaction)
  • DELETE /fruits/{id} deletes a fruit by ID (inside a transaction)

You also notice that we access the JPA data through the Fruit class directly. This is called the “active record pattern”. If you have more complex logic or custom queries, you should look at the “repository pattern.”

Step 5. test your persistence layer

testing is essential to make sure that your application behaves correctly. Many Java tutorials use in-memory databases like H2 for testing, because they are easy to set up. But there is a problem: H2 is not PostgreSQL. It behaves differently, supports different SQL features, and sometimes code that works in H2 will fail in PostgreSQL.

Quarkus Dev Services solves this problem by starting a real PostgreSQL database in a container when you run your tests. This means you test against the same database you will use in production, without having to install anything manually. It gives you more realistic tests and more confidence that your code will work in real life.

Let rename and modify the GreetingResourcetest.java to src/test/java/com/ibm/developer/FruitResourcetest.java:

package com.ibm.developer;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.hasItems;

import org.junit.jupiter.api.test;

import io.quarkus.test.junit.Quarkustest;

@Quarkustest
public class FruitResourcetest {

    @test
    public void testFruitEndpoint() {
        // Insert a fruit
        given()
                .body("{\"name\":\"Banana\", \"color\":\"Yellow\"}")
                .header("Content-Type", "application/json")
                .when().post("/fruits")
                .then()
                .statusCode(200);

        // Check that it is listed
        given()
                .when().get("/fruits")
                .then()
                .statusCode(200)
                .body("name", hasItems("Banana"));
    }
}

If your application is still running, hit “r” in the console to see the tests run in “continous test mode”. You can also execute the tests by using Maven directly:

./mvnw test

When the test runs, Quarkus will:

  • Detect that PostgreSQL is needed.
  • Start a PostgreSQL Dev Service in a container using Podman or Docker.
  • Run the tests against this real database.

Stop the container when the tests are finished.

A note about mvnw

So far, we used the Quarkus CLI (quarkus create app and quarkus dev) to manage and run our project. Behind the scenes, the Quarkus CLI uses Maven (or Gradle if you chose that build tool).

Every Quarkus project that is generated with the CLI includes a file called mvnw (short for Maven Wrapper). This is a small script that downloads the correct version of Maven automatically and runs it for you. You don’t need to install Maven yourself.

On Linux or macOS, you run it as ./mvnw. On Windows, you run it as mvnw.cmd. This ensures that everyone on your team uses the same Maven version, which avoids “it works on my machine” problems.

When should you use the Quarkus CLI versus ./mvnw? Use the Quarkus CLI for developer-friendly commands like create app, dev mode, or adding extensions. Use ./mvnw for standard Maven build goals like package, test, or clean.

You will often switch between the two, depending on what you want to do.

Summary

You learned how Panache simplifies database persistence by providing an active record pattern that reduces boilerplate code, and how Dev Services automatically manages database containers so you can test against production-like databases without manual setup. You also discovered how Quarkus handles transactions declaratively with @Transactional, ensuring data consistency with minimal code.