Building Reactive Microservices with Quarkus + Mutiny

by DIdin J. on Apr 16, 2026 Building Reactive Microservices with Quarkus + Mutiny

Learn to build reactive microservices with Quarkus and Mutiny using Uni, Multi, REST APIs, and SSE streams for scalable, non-blocking Java applications.

Reactive systems are becoming essential for modern backend applications that need to handle large numbers of concurrent requests efficiently. Traditional blocking applications often struggle under heavy load because each request consumes a dedicated thread.

With Quarkus and Mutiny, building highly scalable reactive microservices in Java becomes significantly easier.

In this tutorial, we will build a reactive product microservice using Quarkus and Mutiny. The service will expose REST endpoints that return asynchronous responses using Uni and Multi.

By the end of this tutorial, you will learn how to:

  • Create a Quarkus reactive microservice
  • Use Mutiny’s Uni and Multi
  • Build non-blocking REST APIs
  • Handle asynchronous transformations
  • Simulate reactive data streams
  • Test reactive endpoints

Let’s get started.

What You Will Learn

In this tutorial, you’ll build:

  • A Product Service
  • Reactive CRUD-style endpoints
  • Streaming endpoint for real-time product updates

Technologies used:

  • Java 21
  • Quarkus 3.x
  • Mutiny
  • RESTEasy Reactive
  • Maven

Prerequisites

Before starting, make sure you have:

  • Java 21 installed
  • Maven installed
  • VS Code or IntelliJ IDEA
  • Basic knowledge of Java REST APIs

Check installed versions:

java -version
java version "21.0.8" 2025-07-15 LTS
Java(TM) SE Runtime Environment (build 21.0.8+12-LTS-250)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.8+12-LTS-250, mixed mode, sharing)
mvn -version

Apache Maven 3.9.10 (5f519b97e944483d878815739f519b2eade0a91d)
Maven home: /Users/didin/.sdkman/candidates/maven/current
Java version: 21.0.8, vendor: Oracle Corporation, runtime: /Users/didin/.sdkman/candidates/java/21.0.8-oracle
Default locale: en_ID, platform encoding: UTF-8
OS name: "mac os x", version: "26.3.1", arch: "aarch64", family: "mac"


Step 1: Create a New Quarkus Project

Use the Quarkus CLI:

quarkus create app com.djamware:quarkus-reactive-microservice \
    --extension='resteasy-reactive,jackson'

Or using Maven:

mvn io.quarkus.platform:quarkus-maven-plugin:3.34.3:create \
    -DprojectGroupId=com.djamware \
    -DprojectArtifactId=quarkus-reactive-microservice \
    -DclassName="com.djamware.ProductResource" \
    -Dpath="/products"

Move into the project:

cd quarkus-reactive-microservice


Step 2: Project Structure

The project structure will look like this:

src
 └── main
     └── java
         └── com
             └── djamware
                 ├── Product.java
                 ├── ProductService.java
                 └── ProductResource.java


Step 3: Create the Product Model

Create Product.java

package com.djamware;

public class Product {
    public Long id;
    public String name;
    public Double price;

    public Product() {
        this.id = null;
        this.name = null;
        this.price = null;
    }

    public Product(Long id, String name, Double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}


Step 4: Create a Reactive Service with Mutiny

Now create ProductService.java

package com.djamware;

import java.time.Duration;
import java.util.List;

import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ProductService {
    private final List<Product> products = List.of(
            new Product(1L, "Laptop", 1200.0),
            new Product(2L, "Mouse", 25.0),
            new Product(3L, "Keyboard", 75.0));

    public Uni<List<Product>> getAllProducts() {
        return Uni.createFrom().item(products);
    }

    public Uni<Product> getProductById(Long id) {
        return Uni.createFrom()
                .item(products.stream()
                        .filter(p -> p.id.equals(id))
                        .findFirst()
                        .orElse(null));
    }

    public Multi<Product> streamProducts() {
        return Multi.createFrom()
                .iterable(products)
                .onItem().call(product -> Uni.createFrom()
                        .nullItem()
                        .onItem().delayIt().by(Duration.ofSeconds(1)));
    }
}


Step 5: Create REST Resource

Now create ProductResource.java

package com.djamware;

import java.util.List;

import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/products")
@Produces(MediaType.APPLICATION_JSON)
public class ProductResource {
    ProductService productService;

    @Inject
    public ProductResource(ProductService productService) {
        this.productService = productService;
    }

    @GET
    public Uni<List<Product>> getAll() {
        return productService.getAllProducts();
    }

    @GET
    @Path("/{id}")
    public Uni<Product> getById(@PathParam("id") Long id) {
        return productService.getProductById(id);
    }

    @GET
    @Path("/stream")
    @Produces(MediaType.SERVER_SENT_EVENTS)
    public Multi<Product> stream() {
        return productService.streamProducts();
    }
}


Step 6: Understanding Uni vs Multi

This is the most important part of Mutiny.

Uni

Uni represents one asynchronous item.

Examples:

  • fetch one record
  • API response
  • database query result
Uni<Product>

Think of it as:

Promise / Future for one result


Step 7: Understanding Multi

Multi represents a stream of items.

Examples:

  • event streams
  • message queues
  • live notifications
  • SSE streaming
Multi<Product>

Think of it as:

asynchronous stream / observable sequence


Step 8: Add Reactive Transformation

Let’s add a price discount transformation.

Update service:

    public Uni<Product> getDiscountedProduct(Long id) {
        return getProductById(id)
                .onItem().transform(product -> {
                    product.price = product.price * 0.9;
                    return product;
                });
    }

This is a clean example of the Mutiny transformation.


Step 9: Run the Application

Start development mode:

./mvnw quarkus:dev

Test endpoints:

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

Streaming:

curl http://localhost:8080/products/stream


Step 10: Example Output

[
  {
    "id": 1,
    "name": "Laptop",
    "price": 1200.0
  },
  {
    "id": 2,
    "name": "Mouse",
    "price": 25.0
  }
]


Why Use Reactive Microservices?

Reactive microservices help with:

  • better scalability
  • Reduced thread blocking
  • efficient resource usage
  • high concurrency
  • cloud-native workloads

This is especially useful for:

  • API gateways
  • event-driven services
  • streaming systems
  • real-time dashboards


Conclusion

In this tutorial, you learned how to build a reactive microservice using Quarkus and Mutiny.

You now understand:

  • Uni
  • Multi
  • reactive REST endpoints
  • SSE streaming
  • asynchronous transformation

This foundation is excellent for building production-ready cloud-native Java microservices.

You can find the full source code on our GitHub.

You can find my first Ebook about Angular 21 + Spring Book 4 JWT Authentication here

We know that building beautifully designed Mobile and Web Apps from scratch can be frustrating and very time-consuming. Check Envato unlimited downloads and save development and design time.

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

Thanks!