Quarkus Reactive REST made easy

Quarkus JAX RS implementation has improved a lot since its first release. Within this tutorial we will show some new features which are available in Quarkus starting from the new reactive REST paradigm.

Quarkus uses SmallRye Mutiny for as main Reactive library. In our first tutorial, we have discussed how to use Mutiny to deliver reactive Hibernate applications: Getting started with Hibernate reactive on Quarkus

We will now dig into Reactive REST Services, showing how you can style REST Services in a modern Quarkus application.

A new Reactive REST framework

The standard model used by REST applications is to use a single Thread for every request. This model, however, is not scalable as you will need one thread for every concurrent request, which places a limit on the achievable concurrency.

To rescue, the reactive execution model enables asynchronous execution and non-blocking IO.

RESTEasy Reactive is the next generation of HTTP framework. It can ensure the best throughput by handling each HTTP request on an IO thread. This model has the following benefits:

  • Firstly, smaller response time as there is no thread context switch.
  • Next, less memory and cpu usage as it decreases the usage of threads.
  • Finally, you are not limited by the number of threads.

Let’s see how to code a Reactive REST application from the grounds up.

Coding a Reactive applications

To start coding our application we can use Quarkus Generator or any other tool (Maven plugin, Quarkus CLI, etc). We need to include the following extension to our project:

quarkus reactive mutiny resteasy

This will add in your project, the following dependency:

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>

Next, let’s modify the default Endpoint class as follows:

@Path("/")
public class ReactiveGreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/greeting/{name}")
    public Uni<String> greeting(String name) {
    	 return Uni.createFrom().item(name)
         .onItem().transform(n -> String.format("hello %s", name));
    }
 

    @GET
    @Produces(MediaType.SERVER_SENT_EVENTS)
    @RestSseElementType(MediaType.TEXT_PLAIN)
    @Path("/stream/{count}/{name}")
    public Multi<String> greetingsAsStream(int count, String name) {

        return Multi.createFrom().ticks().every(Duration.ofSeconds(1))
        .onItem()
        .transform(n -> String.format("hello %s - %d", name, n))
        .select()
        .first(count);

    
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public  Uni<String> hello() {
    	return Uni.createFrom().item("hello");
    }
}

The main difference compared with classic REST services are Uni and Multi events. As discussed in our first tutorial, Mutiny offers two types that are both event-driven and lazy:

  • A Uni emits a single event (an item or a failure). A good example is the result of sending a message to a message broker queue.
  • A Multi emits multiple events (n items, 1 failure or 1 completion). A good example is receiving messages from a message broker queue.

Therefore, our Endpoint will produce the following events:

  • A Uni event which sends the text “hello” as event
  • A Uni event which publishes a text transformed from the incoming request parameter “name”
  • Finally, a Multi event which streams a set of greetings events transforming the incoming request parameter “name”
New Parameter Context injection

Did you notice our Endpoint does not declare any  @PathParam or @RestPath ? With RESTEasy Reactive you don’t need it as long as your parameter has the same name as a path parameter

Handling Server side events

In conclusion, to handle server side events, we will add a minimal AngularJS Module which will poll the REST Endpoint with some Form parameters. We will show just the main code snipped – check the source code for the full example:

<!doctype html>
<html ng-app="myApp">

<head>
  <meta charset="utf-8">
  <title>Server-Sent Events with Quarkus and AngularJS</title>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>

<body ng-controller="myController">
  <form ng-submit="startEventStream()">
    <label for="countInput">Number of events:</label>
    <input type="number" id="countInput" ng-model="count" min="1" max="10" required>
    <br>
    <label for="nameInput">Name:</label>
    <input type="text" id="nameInput" ng-model="name" required>
    <br><br>
    <button type="submit">Start Event Stream</button>
  </form>

  <div id="eventContainer"></div>

  <script type="text/javascript">
    angular.module('myApp', [])
      .controller('myController', ['$scope', '$http', function($scope, $http) {
        $scope.startEventStream = function() {
          var count = $scope.count;
          var name = $scope.name;
          var eventSource = new EventSource('/stream/' + count + '/' + name);
          var container = document.getElementById('eventContainer');
          var eventCount = 0;

          eventSource.addEventListener('message', function(event) {
            if (eventCount < count) {
              var p = document.createElement('p');
              p.textContent = event.data;
              container.appendChild(p);
              eventCount++;
            } else {
              eventSource.close();
            }
          });
        }
      }]);
  </script>
</body>

</html>

Running the application

You can build and run the application as usual with:

mvn install quarkus:dev

Next, you can test the following endpoints:

  • /hello – returns “hello”
  • /greeting/name – returns a greeting for name
  • /streaming/name/count – returns a count of SSE with name

Moreover, the home page (index.html) captures the streaming events using AngularJS:

Bonus Tip: You can also test Server side events with just the curl command line tool:

curl -N -H 'Accept: text/event-stream' http://localhost:8080/stream/5/John

Adding per-class Exception Mapper

In the JAX-RS specification all exception mapping is done in a global manner. However, when using RESTEasy Reactive you can handle exceptions with a specific JAX-RS Resource Class through the annotation @ServerExceptionMapping:

@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/greeting/{name}")
public Uni<String> greeting(String name) {
   if (name == null || name.size().trim() == 0) {
       throw new IllegalArgumentException();
   } 
	 return Uni.createFrom().item(name)
     .onItem().transform(n -> String.format("hello %s", name));
}


@ServerExceptionMapper(IllegalArgumentException.class)
public Response handleIllegal() {
   return Response.status(409).build();
}

In the above example, the Exception mapper will be called only for exceptions thrown in the same class. If you want to intercept exceptions globally, simply define them outside of the Endpoint. For example:

class ExceptionMappers {
    @ServerExceptionMapper
    public RestResponse<String> mapException(IllegalArgumentException exc) {
        return RestResponse.status(Response.Status.CONFLICT, "Illegal argument!");
    }
}

Mutiny deprecated methods

The first release of Mutiny includes the transform() method in Mutiny to transform the items emitted by the stream into a new type. A major limitation occurs when you want to perform two or more operations on the stream. Then, you would have to chain multiple transform() calls, which could quickly become unwieldy.

To address these limitations, the Mutiny team introduced the select() method. The select() method allows you to specify one or more operators to apply to the stream, each of which performs a specific operation on the stream. The operators are applied sequentially, which means you can perform multiple operations on the stream using a single select() call. This makes it easier to write complex stream transformations and avoids the need for long chains of transform() calls.

Let’s take a look at an example to see how select() can be used to achieve the same functionality as transform(). Suppose we have a Multi that emits numbers, and we want to transform each number by squaring it and adding 1. Here is how we could do it using transform():

Multi<Integer> numbers = ...;

Multi<Integer> transformed = numbers.transform().byMapping(n -> n * n + 1);

And here is how we could achieve the same result using select():

Multi<Integer> transformed = numbers.select().map(n -> n * n + 1);

In this example, we use the map() operator to transform each emitted item by squaring it and adding 1. The map() operator is similar to the byMapping() operator used in the transform() example, but it is applied directly to the select() method.

The following OpenRewrite plugin, automatically migrates Mutiny deprecated methods: https://docs.openrewrite.org/running-recipes/popular-recipe-guides/quarkus-2.x-migration-from-quarkus-1.x

Conclusion

In conclusion, reactive REST services have become increasingly popular in recent years, and Quarkus and Mutiny provide powerful tools for building high-performance and scalable applications. By leveraging the reactive programming paradigm and taking advantage of Quarkus’ lightweight and efficient nature, developers can easily build RESTful APIs that are responsive, resilient, and adaptable to changing workloads. Mutiny’s fluent and intuitive API also simplifies the development process, allowing developers to focus on building business logic instead of managing low-level details.

Source code for this tutorial: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/jaxrs-reactive

Found the article helpful? if so please follow us on Socials