Learn Quarkus - Reactive Programming & Vert.x
Episode 15 of 24

Learn Quarkus - Reactive Programming & Vert.x

This episode covers reactive programming in Quarkus: the reactive stack with Vert.x, the Uni and Multi concepts for asynchronous processing, reactive messaging with Kafka, AMQP, or MQTT, and when to use reactive versus imperative.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Modern applications must serve thousands of concurrent requests. The traditional blocking model holds one thread per request — wasteful and not scalable. Reactive programming offers an alternative: one thread serves many operations in a non-blocking way.

Episode 15 covers the Quarkus reactive stack built on Vert.x, the Uni and Multi concepts for asynchronous processing, reactive messaging with Kafka, AMQP, or MQTT, and a guide to when to use reactive versus imperative.

The Reactive Stack with Vert.x in Quarkus

Vert.x at the Core of Quarkus

Vert.x is an event-driven toolkit that is the non-blocking foundation of Quarkus. RESTEasy Reactive, reactive routes, and the event loop all run on top of Vert.x. Its main model: an event loop that processes events non-blocking and worker threads for blocking work.

Every Quarkus application has several event loops by default. REST requests are processed on the event loop; blocking work (like synchronous JDBC) is delegated to workers.

Reactive Routes

Besides JAX-RS, Quarkus offers Vert.x-based reactive routes: annotate a method with @Route(path = "/api/status", methods = HttpMethod.GET) and accept a RoutingContext to write the response directly. For simple JSON APIs, RESTEasy Reactive (episode 5) is more convenient; reactive routes are useful for low-level control.

Uni and Multi: The Asynchronous Concepts

Uni for a Single Result

Uni represents a single result (or a single failure) in the future. It's the reactive equivalent of CompletableFuture:

JavaUni for a single result
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
 
@ApplicationScoped
public class QuoteService {
 
    public Uni<String> ambilQuote() {
        return Uni.createFrom().item("Halo, dunia reactive!")
            .onItem().delayIt().by(java.time.Duration.ofMillis(100));
    }
}

Uni.createFrom().item("...") creates a Uni that succeeds immediately. The .onItem() and .delayIt() chain builds a transformation pipeline without blocking a thread.

Multi for Data Streams

Multi represents a stream of several items. It's suitable for streaming data or events:

JavaMulti producing a data stream
import io.smallrye.mutiny.Multi;
import java.time.Duration;
 
public Multi<Integer> hitung() {
    return Multi.createFrom()
        .range(1, 10)
        .onItem().transform(n -> n * n);
}

Multi.createFrom().range(1, 10) emits 1 through 9, then .transform(n -> n * n) turns each item into its square. Consumers can subscribe to receive items one by one.

Using Uni in an Endpoint

RESTEasy Reactive supports Mutiny types directly:

JavaReactive endpoint
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
 
@Path("/api/quote")
public class QuoteResource {
 
    @Inject
    QuoteService quoteService;
 
    @GET
    public Uni<String> quote() {
        return quoteService.ambilQuote();
    }
}

The method returns a Uni<String> — the request doesn't block a thread; the result is sent when the Uni completes. This is the core model of reactive REST.

Reactive Messaging with Kafka, AMQP, or MQTT

Reactive Messaging

SmallRye Reactive Messaging connects your application to message brokers asynchronously. Add the extension:

Adding the Kafka extension
./mvnw quarkus:add-extension -Dextensions=reactive-messaging-kafka

Producing and Consuming

JavaMessage producer and consumer
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.reactive.messaging.Channel;
import org.eclipse.microprofile.reactive.messaging.Emitter;
import org.eclipse.microprofile.reactive.messaging.Incoming;
import io.smallrye.mutiny.Uni;
 
@ApplicationScoped
public class OrderMessaging {
 
    @Channel("orders-out")
    Emitter<String> ordersEmitter;
 
    public void kirimOrder(String order) {
        ordersEmitter.send(order);
    }
 
    @Incoming("orders-in")
    public void prosesOrder(String order) {
        System.out.println("Order diterima: " + order);
    }
}

@Channel("orders-out") sends to a Kafka channel, @Incoming("orders-in") receives from another channel. The connector configuration:

Kafka channel configuration
mp.messaging.outgoing.orders-out.connector=smallrye-kafka
mp.messaging.outgoing.orders-out.topic=orders
mp.messaging.incoming.orders-in.connector=smallrye-kafka
mp.messaging.incoming.orders-in.topic=order-events
mp.messaging.incoming.orders-in.auto.offset.reset=earliest

The command ./mvnw quarkus:add-extension -Dextensions=reactive-messaging-kafka adds the Kafka connector. For AMQP or MQTT, replace the connector with smallrye-amqp or smallrye-mqtt.

When to Use Reactive vs Imperative

When to Use Imperative

  • Simple business code that's mostly blocking (synchronous JDBC, third-party sync).
  • The team isn't yet familiar with reactive and mutiny observability.
  • Moderate concurrency load and latency isn't critical.

When to Use Reactive

  • High concurrency scale: thousands of requests per second.
  • I/O intensive: many external calls, databases, and messaging.
  • Streaming data and event-driven architecture.
  • Tight integration with Vert.x and messaging.

Practical Guidance

Combining both is valid: use imperative for simple CRUD and reactive for high-I/O paths. Quarkus makes the gradual transition easy — reactive and imperative resources can coexist in a single application.

Wrap-Up

Episode 15 opens up the reactive world of Quarkus: understanding Vert.x as the non-blocking foundation, the Uni concept for a single result and Multi for data streams, reactive messaging with Kafka, AMQP, and MQTT, and a guide to when to use reactive versus imperative.

Key takeaways:

  • Vert.x is the non-blocking foundation of the entire Quarkus stack.
  • Uni represents a single async result; Multi represents a stream of items.
  • RESTEasy Reactive supports Mutiny types directly in endpoints.
  • Reactive Messaging connects your application to message brokers.
  • Kafka, AMQP, and MQTT are supported through different connectors.
  • Reactive suits high concurrency loads and I/O-intensive work.
  • Reactive and imperative can coexist in a single application.

In episode 16 we'll cover native images and startup optimization — Quarkus native images with GraalVM or Mandrel, build-time augmentation and AOT compilation, startup and memory optimization, as well as debugging native images and compatibility tradeoffs.

Learn Quarkus - Reactive Programming & Vert.x | Learn Quarkus