Thinking about Quarkus usually means thinking about speed, performance, an immediate now! — but… do you think about it reactively or sequentially? Sequential thinking is the way we’re traditionally taught, while reactive thinking is how we eventually end up learning (whether we want to or not). With Mutiny, operations in Quarkus start to make sense reactively, flowing in parallel and leveraging multiple threads…

In this post, we’ll walk through the first steps with Mutiny in Quarkus, a framework designed to make our lives easier in a reactive world that can be complex and hard to grasp at first. We’ll cover everything from what Uni and Multi are to how to transform data and handle errors. Welcome to the reactive world in Quarkus.

Why use Mutiny?

1 We want (and need) to move to reactivity

The most important thing is knowing what we need, above what we want. We might want a reactive-based project when we don’t actually need one—and that would be like putting a Ferrari engine into a Seat 600.

Therefore, if we truly need reactive programming in our Quarkus project, we have to take that leap into reactivity.

2 Once decided: why Mutiny?

3 Key concepts: Uni and Multi

Mutiny revolves around two key concepts:

Once we’re clear on what each one represents, how do we work with them? What operations can we perform?

The easiest way is to look at an example:

Uni.createFrom().item("Hello")
    .onItem()
    .transform(s -> s + " world")
    .onItem()
    .invoke(s->System.out.println("Broadcast"+s))
.onFailure().recoverWithItem("Error over here");

In that code, the following happens:

Therefore, the most common operators in Mutiny are:

Uni.combine().all()unis(...).combinedWith(...)

Other operators that we frequently use with Multi are already well known from Java, such as merge(), concatenate(), select(), and many more that you can find here.

4 Preparing our Quarkus project for Mutiny

Once we’ve decided that we really do need Mutiny and reactivity in our Quarkus project, we’ll go step by step (starting from project creation):

  1. Generate our Quarkus project, either via code.quarkus.io or using mvn.
mvn io.quarkus.platform:quarkus-maven-plugin:create \
    -DprojectGroupId=com.example \
    -DprojectArtifactId=mutiny-demo \
    -DclassName="com.example.GreetingResource" \
    -Dpath="/hello"
  1. If the Mutiny dependency is not present, we add it manually to our pom:
<dependency>
  <groupId>io.quarkus</groupId>
 <artifactId>quarkus-mutiny</artifactId>
</dependency>

Or via mvn:

mvn quarkus:add-extension -Dextensions=mutiny
  1. Once the project has been generated, we are going to create a basic endpoint that returns a greeting, as one should do when seeing someone:
package com.example;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.smallrye.mutiny.Uni;

@Path("/hello")
public class GreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public Uni<String> hello() {
        return Uni.createFrom().item("Hello from Quarkus + Mutiny")
                  .onItem().transform(s -> s + " — effective as of " + System.currentTimeMillis());
    }
}

And just like that, we’ve built our very first Mutiny endpoint with Quarkus!

5 Using Multi for streams and lists

In our first endpoint, we relied solely on Uni. Now, let’s try the other key element: Multi. As a reminder, we use Multi when we expect multiple results (such as streams or lists).

If we had an endpoint called words:

@Path("/words")
public class WordsResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Multi<String> words() {
        return Multi.createFrom().items("one", "two", "three")
            .onItem().transform(String::toUpperCase);
    }
}

When we make the request (localhost:8080/words), Quarkus will send us a JSON stream (if our client/server supports it). You can also combine Multi with Uni to transform each element of our Multi into an individual Uni:

Multi.createFrom().items("a", "b", "c")
     .onItem().transformToUni(s -> Uni.createFrom().item(s + "_x"))
     .concatenate();

Another example we can find with Multis is the combination between them, that is, all to the Multi!

public Uni<String> combined() {
    Uni<String> u1 = Uni.createFrom().item("A").onItem().delayIt().by(Duration.ofMillis(100));
    Uni<String> u2 = Uni.createFrom().item("B").onItem().delayIt().by(Duration.ofMillis(200));

    return Uni.combine().all().unis(u1, u2)
        .combinedWith(list -> {
            String s1 = (String) list.get(0);
            String s2 = (String) list.get(1);
            return s1 + "-" + s2;
        });
}

In this example, combined() waits for both operations to complete and then combines them. It does not return a result until both have finished.

6 Common mistakes and how to avoid them

In Quarkus, if we define an endpoint that returns a Uni (for example, the GET endpoint used in the examples), Quarkus automatically takes care of subscribing. That is, when the Uni emits a value, Quarkus transforms it into an HTTP response (for example, a 200 OK with the content of the Uni). Do not use subscribe() manually outside of testing contexts.

We should not use blocking calls inside our applications (Thread.sleep, blocking database access, etc.). If we need to perform a blocking operation, the best approach is to convert it into a Uni executed on a different thread or use .runSubscriptionOn(...).

We must avoid mutating lists or external variables inside operators, as this can lead to unexpected results in a reactive environment.

Always design operations from the beginning using Uni and Multi, ensuring that everything either returns these types or works directly with them.

Mutiny has strong testing support—so let’s make use of it.

Conclusion

We now have our first foundations to take another step into reactive programming (as we’ve already seen in other posts focused on RxJava) but this time with Quarkus.

We’ve met Multi and Uni, our new friends, and seen how to combine them in different operations. If this has been your first contact with reactive programming, you probably felt the jump more strongly, as it forces us to change the way we think.

However, one of the most important things we need to do is try and fail, fail and try again. That’s how we truly learn reactive programming and make it feel familiar. Looking forward to your thoughts in the comments!

References

Tell us what you think.

Comments are moderated and will only be visible if they add to the discussion in a constructive way. If you disagree with a point, please, be polite.

Subscribe