Java + Quarkus 2

Quarkus has, from its beginning, provided core features for coding Java programs in both imperative and reactive style. With the new 2.2 release, Quarkus continues to improve in terms of network-related features, reactive programming, and integration with the Eclipse Vert.x event bus. For example, RESTEasy Reactive in Quarkus is a new JAX-RS implementation based on the Vert.x layer that achieves much higher throughput by handling reactive events on the non-blocking I/O thread.

The Red Hat build of Quarkus 2.2 provides production support for the RestEasy Reactive extensions, with additional features:

  • Multipart/form-data request handling
  • Executing a non-blocking method by default
  • Determining the dispatch strategy by method signatures
  • Providing a performance score dashboard for reactive endpoints

These new features deliver improved performance improvements when developing reactive applications.

This article demonstrates how you can improve the performance of a Quarkus reactive application by using RESTEasy Reactive features and the Quarkus Dev UI. Download the repository from GitHub to follow along with this tutorial.

Check REST reactive endpoints in the Quarkus Dev UI

First things first: Run Quarkus Dev Mode using the following Maven command in your project directory where you cloned the example:

$ ./mvnw quarkus:dev

Press d after the example application starts and displays the following messages:

--
Tests paused
Press [r] to resume testing, [o] Toggle test output, [h] for more options>

A new web browser will open to access the Quarkus Dev UI, shown in Figure 1. In the RESTEasy Reactive box, click Endpoint scores.

In the Quarkus Dev UI, the RESTEasy Reactive tile offers an "Endpoint scores" link.
Figure 1. In the Quarkus Dev UI, the RESTEasy Reactive tile offers an "Endpoint scores" link.

You will then be taken to the Quarkus REST scores console, which shows the behavior of four REST reactive endpoints with scores and colors (see Figure 2).

The REST scores console shows the performance of endpoints through scores and colors.
Figure 2. The REST scores console shows the performance of endpoints through scores and colors.

Note that two endpoints have a score of 100 with the color green, but the /hello() endpoint has a score of only 33 in red and the /hello/stream/{count}/{name} endpoint has a score of 66 in yellow. Before we investigate why two endpoints are not performing well, let's do a local test to see whether they work properly.

Execute the following curl commands to invoke the two REST reactive endpoints. For the /hello endpoint, enter:

$ ​​curl localhost:8080/hello
hello 

For the /hello/stream/{count}/{name} endpoint, enter:

$ curl localhost:8080/hello/stream/3/daniel 
data:hello daniel - 0 
data:hello daniel - 1 
data:hello daniel - 2

These commands should complete without errors. Go back to the scores dashboard in the Quarkus Dev UI and click GET /hello. The detail scores will appear as shown in Figure 3.

The detailed view of an endpoint breaks scores down into Resource, Writer, and Execution.
Figure 3. The detailed view of an endpoint breaks scores down into Resource, Writer, and Execution.

Three measurements show whether a REST reactive application can be optimized further. For example, the hello endpoint is optimized only for resource measurement. This means the endpoint is accessible and returns a successful output, which you already tested locally. In contrast, the Writer and Execution measurements show failures with 0% scores.

Tune the slow endpoints to optimize performance

Open the ReactiveGreetingResource.java file in the src/main/java/org/acme/getting/started directory and take a look at the hello() method:

@GET
@Produces(MediaType.TEXT_PLAIN)
public Object hello() {
return "hello";
}

The return type of the method is currently defined with a type of Object. It needs to be specified as a String to avoid unnecessary object conversion during reactive application execution. Therefore, update the return type as shown in the following snippet (the change is highlighted in bold):

@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}

Save the ReactiveGreetingResource.java file, then reload the scoreboard page in the Dev UI. You should find that the performance has improved, because the Writer measurement has been fixed and the score has increased to 66 as shown in Figure 4.

The Writer score is now 100%.
Figure 4. The Writer score is now 100%.

Non-blocking I/O

Let's fix the remaining performance issue in the hello() method, shown by the Execution measurement. RESTEasy Reactive, in the Red Hat build of Quarkus 2.2, automatically determines whether a particular method runs on a non-blocking I/O thread asynchronously or on a worker thread synchronously, based on the method's signature. The hello() method returns a String signature that dispatches a blocking worker thread. The blocking thread reduces reactive processing performance compared to the non-blocking I/O thread.

Therefore, you need to override the default dispatch strategy to make it use a non-blocking I/O thread, using the @NonBlocking annotation. Go back to the ReactiveGreetingResource.java file and add a @NonBlocking annotation as follows:

@GET
@Produces(MediaType.TEXT_PLAIN)
@NonBlocking
public String hello() {
return "hello";
}

Save the ReactiveGreetingResource.java file, then reload the scoreboard page in the Dev UI. You should now have 100 scores everywhere, thanks to optimizing the hello() method's performance (Figure 5).

All three scores are now 100%.
Figure 5. All three scores are now 100%.

Now let's fix the other endpoint, /hello/stream/{count}/{name}. Move to the greetingsAsStream() method in the ReactiveGreetingResource.java file:

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

The method signature is already specified with Multi to dispatch a non-blocking I/O thread. However, an unnecessary @Blocking annotation appears in the method, overriding the default dispatch strategy. Therefore, you need to remove that annotation or comment it out. Update the greetingsAsStream method as shown in the following example:

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

Save the file and reload the scores page in the Dev UI. You should now have 100% scores everywhere because the greetingsAsStream() method has been optimized, as shown in Figure 6.

The second endpoint now has three 100% scores.
Figure 6. The second endpoint now has three 100% scores.

Figure 7 shows that you have optimized the performance of all endpoints.

The REST scores console shows 100% scores for all endpoints.
Figure 7. The REST scores console shows 100% scores for all endpoints.

How to get started with Quarkus

This article has shown how developers optimize reactive applications with Quarkus in the inner-loop development phase. Release 2.2 also provides awesome features to improve developers' productivity, including continuous testing, improvements in the Quarkus command-line interface (CLI) and the Dev UI, and Dev Services.

To learn more, check out the following resources:

Last updated: October 6, 2022