Build Reactive APIs in Quarkus Using Mutiny and RESTEasy Reactive

by Didin J. on Jul 04, 2025 Build Reactive APIs in Quarkus Using Mutiny and RESTEasy Reactive

Build reactive REST APIs using Quarkus, Mutiny, and RESTEasy Reactive with Hibernate Reactive and Panache. Fast, scalable, and non-blocking Java APIs.

In the era of cloud-native and event-driven applications, building high-performance, scalable, and responsive APIs is critical. Traditional imperative models often fall short when dealing with high concurrency or IO-bound workloads. That’s where reactive programming comes in—and Quarkus makes it easy and efficient with Mutiny and RESTEasy Reactive.

In this tutorial, you will learn how to build reactive RESTful APIs using Quarkus, leveraging Mutiny for reactive programming and RESTEasy Reactive for fast, non-blocking HTTP endpoints. We’ll create a CRUD API backed by an in-memory database to demonstrate reactive principles in action.

By the end, you’ll understand how to:

  • Build non-blocking REST APIs using RESTEasy Reactive

  • Use Mutiny’s Uni and Multi types to handle async operations

  • Connect to a reactive database using Panache and Hibernate Reactive

  • Write and test endpoints that return fast, resilient responses

Let’s get started!


Project Setup

We’ll use the Quarkus CLI to quickly scaffold the project. Make sure you have the following installed:

Prerequisites

  • Java 17+

  • Maven 3.9+

  • Quarkus CLI:
    Install via SDKMAN or Homebrew:

sdk install quarkus
# or
brew install quarkus

Create a New Quarkus Project

Open a terminal and run:

quarkus create app com.djamware:quarkus-reactive-api \
  --no-code \
  --gradle \
  --extension='resteasy-reactive, resteasy-reactive-jackson, hibernate-reactive-panache, reactive-pg-client, jdbc-postgresql, smallrye-health, smallrye-openapi'

Replace --gradle with --maven if you prefer Maven over Gradle.

This will generate a project structure with all necessary dependencies to build reactive APIs and connect to PostgreSQL reactively.

Project Structure Overview

After creating the project, you’ll see something like:

quarkus-reactive-api/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/djamware/
│   │   └── resources/
│   └── test/
├── build.gradle or pom.xml
├── application.properties

We'll start building our model, resource, and service layers in the next steps.

Configure Application Properties

Open src/main/resources/application.properties and set up the database and HTTP config:

# HTTP port
quarkus.http.port=8080
# PostgreSQL reactive datasource
quarkus.datasource.db-kind=postgresql
quarkus.datasource.reactive.url=postgresql://localhost:5432/reactivedb
quarkus.datasource.username=djamware
quarkus.datasource.password=dj@mw@r3
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
# Enable health and OpenAPI
quarkus.smallrye-health.root-path=/health
quarkus.swagger-ui.always-include=true

Make sure PostgreSQL is running and a database named reactivedb exists.

psql postgres -U djamware
create database reactivedb;
\q


Create the Entity and Repository

We'll create a simple Product entity for this tutorial and expose reactive CRUD operations.

1. Define the Product Entity

Create a new file:
📄 src/main/java/com/djamware/model/Product.java

package com.djamware.model;

import io.quarkus.hibernate.reactive.panache.PanacheEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.Column;

@Entity
public class Product extends PanacheEntity {

    @Column(nullable = false)
    public String name;

    @Column
    public String description;

    @Column(nullable = false)
    public double price;

    // No need for getters/setters — Panache handles it
}

We're extending PanacheEntity, which gives us an id field and built-in reactive CRUD operations like .persist(), .findAll(), and .delete().

2. Create the Repository (Optional but Recommended for Abstraction)

You can use Panache directly from the resource class, but for separation of concerns, let’s create a repository.

📄 src/main/java/com/djamware/repository/ProductRepository.java

package com.djamware.repository;

import com.djamware.model.Product;
import io.quarkus.hibernate.reactive.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ProductRepository implements PanacheRepository<Product> {
    // Custom query methods can go here
}

3. Add Sample Data (Optional for Testing)

You can initialize some test data at startup. Create:

📄 src/main/java/com/djamware/bootstrap/DataInitializer.java

package com.djamware.bootstrap;

import com.djamware.model.Product;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Singleton;

@Singleton
public class DataInitializer {

    void onStart(@Observes StartupEvent ev) {
        Product p1 = new Product();
        p1.name = "Quarkus Mug";
        p1.description = "Ceramic mug with Quarkus logo";
        p1.price = 12.99;

        Product p2 = new Product();
        p2.name = "Reactive T-shirt";
        p2.description = "Soft cotton T-shirt for devs";
        p2.price = 24.99;

        Product.persist(p1, p2).subscribe().with(
            success -> System.out.println("Data initialized"),
            failure -> failure.printStackTrace()
        );
    }
}

This uses Mutiny’s reactive subscribe().with(...) pattern to handle persistence asynchronously.

What We Have Now

You now have:

  • A reactive Product entity managed by Hibernate Reactive

  • A repository that can be injected for custom logic

  • (Optional) Data initialization logic using Mutiny


Create the REST Resource

We’ll expose a RESTful API for our Product entity using Quarkus’ reactive stack:

  • HTTP layer: RESTEasy Reactive

  • Async types: Mutiny’s Uni and Multi

  • Backend: Hibernate Reactive + Panache

 

1. Create ProductResource

File path:
src/main/java/com/djamware/resource/ProductResource.java

package com.djamware.resource;

import com.djamware.model.Product;
import com.djamware.repository.ProductRepository;

import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
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;
import jakarta.ws.rs.core.Response;

@Path("/api/products")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ProductResource {

    @Inject
    ProductRepository repository;

    @GET
    public Multi<Product> getAll() {
        return repository.listAll()
                .onItem().transformToMulti(products -> Multi.createFrom().iterable(products));
    }

    @GET
    @Path("/{id}")
    public Uni<Response> getById(@PathParam("id") Long id) {
        return repository.findById(id)
                .onItem().ifNotNull().transform(product -> Response.ok(product).build())
                .onItem().ifNull().continueWith(Response.status(Response.Status.NOT_FOUND)::build);
    }

    @POST
    public Uni<Response> create(Product product) {
        return repository.persist(product)
                .map(p -> Response.status(Response.Status.CREATED).entity(p).build());
    }

    @PUT
    @Path("/{id}")
    public Uni<Response> update(@PathParam("id") Long id, Product updated) {
        return repository.findById(id)
                .onItem().ifNotNull().transformToUni(product -> {
                    product.name = updated.name;
                    product.description = updated.description;
                    product.price = updated.price;
                    return repository.persist(product)
                            .replaceWith(Response.ok(product).build());
                })
                .onItem().ifNull().continueWith(Response.status(Response.Status.NOT_FOUND)::build);
    }

    @DELETE
    @Path("/{id}")
    public Uni<Response> delete(@PathParam("id") Long id) {
        return repository.deleteById(id)
                .map(deleted -> deleted
                        ? Response.noContent().build()
                        : Response.status(Response.Status.NOT_FOUND).build());
    }
}

Notes

  • Multi<Product> — reactive stream for multiple items (like Flux in Project Reactor)

  • Uni<Product> — async wrapper for a single item (like Mono)

  • Response — JAX-RS HTTP responses

  • .onItem().ifNotNull() and .onItem().ifNull() — Mutiny-style flow control

Optional: Test It

If Quarkus is running (./gradlew quarkusDev or ./mvnw quarkus:dev), test the API with:

curl http://localhost:8080/api/products

Or visit Swagger UI:
👉 http://localhost:8080/q/swagger-ui

What’s Next?

You now have a fully functional reactive REST API! Next sections you can add:

  • ✅ Testing with REST Assured Reactive

  • ✅ Add CORS and validation

  • ✅ Add OpenAPI/Swagger annotations

  • ✅ Dockerize the app (optional)


Final Testing & OpenAPI Documentation

Now that our reactive API is complete, let’s verify everything works as expected and expose the API docs for easy testing and sharing.

Test the Reactive API

Start the Quarkus dev server (if not already running):

./gradlew quarkusDev
# or
./mvnw quarkus:dev

Now test your endpoints:

🔍 Get All Products

curl http://localhost:8080/api/products

🔍 Get Product by ID

curl http://localhost:8080/api/products/1

Create a New Product

curl -X POST http://localhost:8080/api/products \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Quarkus Hoodie",
    "description": "Warm hoodie for developers",
    "price": 45.00
}'

✏️ Update a Product

curl -X PUT http://localhost:8080/api/products/1 \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Updated Hoodie",
    "description": "Updated description",
    "price": 49.99
}'

Delete a Product

curl -X DELETE http://localhost:8080/api/products/1

Enable and Use OpenAPI (Swagger UI)

Quarkus makes it easy to document your API using SmallRye OpenAPI.

It’s already included in your dependencies:

--extension='smallrye-openapi, resteasy-reactive-jackson'

🔍 Access OpenAPI UI

(Optional) Add OpenAPI Annotations

You can improve the docs by annotating your resource:

    @GET
    @Operation(summary = "Get all products", description = "Returns a list of all products")
    public Multi<Product> getAll() {
        return repository.listAll()
                .onItem().transformToMulti(products -> Multi.createFrom().iterable(products));
    }

Other annotations to consider:

@APIResponse(responseCode = "200", description = "Successful Operation")
@APIResponse(responseCode = "404", description = "Product Not Found")


Final Notes

You’ve just built a fully reactive REST API using:

  • 🔄 Quarkus RESTEasy Reactive for ultra-fast HTTP endpoints

  • ⚙️ Hibernate Reactive with Panache for async DB access

  • Mutiny for elegant reactive flows (Uni, Multi)

  • 📘 OpenAPI for documentation

This stack is perfect for building scalable cloud-native services that are responsive under load and ready for event-driven or microservices architectures.


Conclusion

In this tutorial, you’ve built a fully reactive RESTful API using Quarkus, combining the power of RESTEasy Reactive, Mutiny, and Hibernate Reactive with Panache. By embracing non-blocking, asynchronous programming, your application is now capable of handling high-concurrency scenarios efficiently, making it ideal for modern microservices, cloud-native deployments, and performance-critical systems.

You’ve learned how to:

✅ Scaffold a Quarkus project with reactive extensions
✅ Define entities and repositories using Hibernate Reactive
✅ Create reactive endpoints with Uni and Multi
✅ Integrate OpenAPI and Swagger UI for documentation
✅ Test reactive behavior using curl or Swagger UI

Quarkus’s reactive stack provides an intuitive and productive way to build high-performance Java applications without the typical complexity of reactive programming. With Mutiny’s expressive API and RESTEasy Reactive’s low-overhead HTTP stack, you're well-equipped to deliver fast, scalable, and resilient APIs.

You can get the full source code on our GitHub.

That's just the basics. If you need more deep learning about Quarkus, Java, and Microservices, you can take the following cheap course:

Thanks!