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
andMulti
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 anid
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
andMulti
-
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 (likeFlux
in Project Reactor) -
Uni<Product>
— async wrapper for a single item (likeMono
) -
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
-
JSON spec: http://localhost:8080/q/openapi
(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:
-
(2025) Quarkus for beginners, everything you need to know.
-
Quarkus - Simple REST API and Unit Tests with JUnit 5
-
Cloud-native Microservices with Quarkus
-
Building Microservices with Quarkus
-
K8s Native Java Microservices on Quarkus - 2022 Edition
-
Deep Dive into AWS Lambda for Java Developers
-
Accessing Relational Databases with Quarkus
Thanks!