Vertx vs Spring WebFlux: performance comparison for consume asynchronous REST APIs.

Michael Felipe Ayala
6 min readJan 31, 2024

--

Introduction

In previous article (Quarkus RestClient vs Spring WebFlux: performance comparison for consume asynchronous REST APIs), we exposed a Quarkus and Spring WebFlux APIs that consume them three external REST API. Then, we test them using JMeter for 80, 90 and 100 requests around one second.

If we review one of the ‘Next Steps’ about the previous Medium post. It says:

Testing and comparing the results between Vertx (Native) and Spring Webflux to process N external Rest APIs.

So, let’s review this task and see the results.

In this article, we will create a new Vertx project to expose a GET API and will reuse the Spring Boot project (see code here) to compare the results between them. Both APIs will be tested using JMeter and we will review how many concurrent requests could support in one second without failures.

Getting Started

First of all, make you have the following software installed.

  • JDK 21
  • Apache Maven 3.9+
  • Eclipse 4.29+
  • JMeter 5.5

Remember to setup JAVA_HOME and M2_HOME before to start.

Architecture

First, we are going to create two Quarkus projects.

For our Quarkus tests projects we are going to use the Quarkus version 3.5+.

Factorial External Api, is a Quarkus project with a Get Rest API.

  • /getFactorialValue: return the factorial value for its input parameter.

Factorial WebFlux, is a Spring Boot project with a Get Rest API.

  • /getFactorialByWebFlux: Use the Spring Web Client implementation to consume three external APIs and merge them after receive all responses using Spring WebFlux.
Architecture Diagram to consume external APIs using Spring WebFlux

For our Spring WebFlux project, we are going to create a Spring Boot test project with version 3.2.0.

Factorial Vertx, is a vertx project created with a Get Rest API.

  • /getFactorialByVertx: Use the Vertx Client Future implementation to consume three external APIs and join them after receive all responses.
Architecture Diagram to consume external APIs using Vertx

Quarkus use Vertx library underneath. However, this Quarkus-Vertx library didn’t offer the java classes used to join the concurrent requests. This is the reason why we use Vertx native library for this demo project.

For our Vertx project, we are going to create a Vertx test project with version 4.5.1.

Now, let’s delve into the specifics of the application code for each project.

Factorial External API

We are going to reuse this Quarkus project created in a previous post. We can review the Java code here.

Factorial WebFlux

We are going to reuse this Spring Boot project created in a previous post. We can review the Java code here.

Factorial Vertx

Create a new project using Vertx Start IO using the following link.

Project configuration using Vertx Start IO

Then, the pom file looks like:

Pom.xml file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.acme</groupId>
<artifactId>factorial-vertx</artifactId>
<version>1.0.0-SNAPSHOT</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-shade-plugin.version>3.2.0</maven-shade-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
<exec-maven-plugin.version>3.0.0</exec-maven-plugin.version>

<vertx.version>4.5.1</vertx.version>
<junit-jupiter.version>5.9.1</junit-jupiter.version>

<main.verticle>org.acme.MainVerticle</main.verticle>
<launcher.class>io.vertx.core.Launcher</launcher.class>

<java.version>21</java.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>

<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<release>21</release>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>${launcher.class}</Main-Class>
<Main-Verticle>${main.verticle}</Main-Verticle>
</manifestEntries>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<outputFile>${project.build.directory}/${project.artifactId}-${project.version}-fat.jar
</outputFile>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>${exec-maven-plugin.version}</version>
<configuration>
<mainClass>io.vertx.core.Launcher</mainClass>
<arguments>
<argument>run</argument>
<argument>${main.verticle}</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>


</project>

Import maven project in Eclipse IDE and add the following class.

You can also see Java code here.

Java Code

MainVerticle, is a java class created automatically after create Vertx project. Let’s overwrite this class to expose the API to be tested.

package org.acme;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.core.json.JsonArray;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.predicate.ResponsePredicate;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class MainVerticle extends AbstractVerticle {

Logger LOGGER = LoggerFactory.getLogger(MainVerticle.class);

@Override
public void start(Promise<Void> startPromise) throws Exception {

// Create a Router
Router router = Router.router(vertx);

// Mount the handler for all incoming requests at every path and HTTP method
router.route("/factorialVertx/list").handler(context -> {

// CALL EXTERNAL APIs
var idProcess = UUID.randomUUID();
WebClient client = WebClient.create(vertx);

int NUMBER_EXTERNAL_APIS = 3;
List<Future<String>> futureList = new ArrayList();
for(int i=1;i<=NUMBER_EXTERNAL_APIS;i++) {
int valueToCalculate = getRandomNumber(2,10);
LOGGER.info(String.format("VERTX, START idProcess [%s] with valueToCalculate1 [%s]", idProcess, valueToCalculate));

Future<String> response = client.get(8080,"localhost","/factorialExternalApi/calculate/" + valueToCalculate+"/uuid/"+idProcess).expect(ResponsePredicate.SC_OK).send()
.map(HttpResponse::bodyAsString).recover(error -> {
LOGGER.error("External API, Error response: "+error.getMessage());
return Future.succeededFuture();
});
futureList.add(response);
}

List<String> res = new ArrayList();
Future.join(futureList)
.onSuccess(result -> {
result.list().forEach(x -> {
if(x != null) {
res.add(x.toString());
}
});
context.json(new JsonArray(res.toString()));
})
.onFailure(error -> {
LOGGER.error("An error exist joining the results "+error);
});

});

// Create the HTTP server
vertx.createHttpServer()
// Handle every request using the router
.requestHandler(router)
// Start listening
.listen(8888)
// Print the port
.onSuccess(server -> LOGGER.info("HTTP server started on port " + server.actualPort()));
}

private int getRandomNumber(int min, int max) {
return (int) ((Math.random() * (max - min)) + min);
}

}

The application will be running using the port 8888.

Run application

Configure Maven Build from eclipse and Run application.

Configure Maven Build for ‘factorial-vertx’ project

Let’s keep the ‘factorial-external-api’ project running too.

Then, test the API from web browser with the following request.

Test result after request the Vertx API

After hit a request for previous API, we can see this API will consume three times the ‘Factorial external API’ with some random values and print the results. The same expected result as ‘Factorial WebFlux’ project described before. Now, we are ready to stress both APIs with JMeter and compare the results.

Testing from JMeter

Let’s use JMeter to send 80, 90, 100, 200 and 300 requests around one second for both APIs and compare results. The following diagrams shows the results summary. It shows the time taken (hh:mm:ss:SSS) and number of success/failure requests for each group or requests.

JMeter results after test the Spring API
JMeter results after test the Vertx API

From the chart, we can establish:

  • For all possible requests number, Vertx and Spring WebFlux process all of them successfully. Furthermore, they show a similar response time. Remember, we are using a random value to establish if a request is slow or fast.
  • For all possible requests number, there is not any failure. This is a good indicator to choose any of these Tools and create a proof of concepts according your needs.
  • We didn’t test more than 300 requests around one second using JMeter. However, we believe the results are going to follow the same patern.

Next steps

  • Increase the number of external Rest APIs to consume per request. For our current demo we have been testing with three.
  • Testing and comparing the results using other load test tool like Bombardier.
  • Deepen about the implementation of unit and integration tests with these two tools. Then, we can establish another point of view to choose any of these.

--

--