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.

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.
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.
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 represents a single result (or a single failure) in the future. It's the reactive equivalent of CompletableFuture:
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 represents a stream of several items. It's suitable for streaming data or events:
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.
RESTEasy Reactive supports Mutiny types directly:
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.
SmallRye Reactive Messaging connects your application to message brokers asynchronously. Add the extension:
./mvnw quarkus:add-extension -Dextensions=reactive-messaging-kafkaimport 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:
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=earliestThe 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.
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.
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:
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.