Vert.x Parameters and Return Values Code Examples
When building high-performance, reactive microservices, Eclipse Vert.x stands out thanks to its non-blocking, event-loop-centric architecture. Mastering how to pass arguments, handle asynchronous returns, and coordinate data flow between verticles, asynchronous methods, and the event bus is an essential skill for any Vert.x developer.
This article explores common patterns and best practices for parameter passing and return value handling in Vert.x through practical code examples.
In traditional synchronous programming, function results are typically obtained immediately via a return statement. However, in the asynchronous world of Vert.x, time-consuming operations—such as database queries or HTTP calls—do not block the calling thread. Consequently, direct returns won't work; instead, developers must rely on callbacks, Future/Promise models, or RxJava to capture results.
The most straightforward way to pass arguments and return results is through functional interfaces like Handler<AsyncResult<T>> or simple closures.
Below is a utility class demonstrating a simulated asynchronous calculation:
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
public class CalculatorService {
private final Vertx vertx;
public CalculatorService(Vertx vertx) {
this.vertx = vertx;
}
// Accepts parameters a, b, and a result handler
public void add(int a, int b, Handler<Integer> resultHandler) {
vertx.executeBlocking(promise -> {
try {
Thread.sleep(1000); // Simulate blocking operation
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
promise.complete(a + b);
}, res -> {
// Return the result via Handler
resultHandler.handle(res.result());
});
}
}
Handling Return Values with Future and Promise
Starting from version 3.8, Vert.x introduced more modern and composable Future and Promise interfaces. These behave similarly to JavaScript Promises or Java's CompletableFuture.
Here is a complete Verticle example showcasing how to define methods that accept parameters and return a Future:
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
public class FutureExampleVerticle extends AbstractVerticle {
@Override
public void start(Promise<Void> startPromise) {
greetAsync("Vert.x")
.compose(greeting -> {
System.out.println("Received return value: " + greeting);
return fetchUserData("1001");
})
.onSuccess(userData -> {
System.out.println("Final user data retrieved: " + userData);
startPromise.complete();
})
.onFailure(err -> {
System.err.println("Operation failed: " + err.getMessage());
startPromise.fail(err);
});
}
private Future<String> greetAsync(String name) {
Promise<String> promise = Promise.promise();
vertx.setTimer(500, id -> {
if (name == null || name.isEmpty()) {
promise.fail("Name cannot be null or empty");
} else {
promise.complete("Hello, " + name + "!");
}
});
return promise.future();
}
private Future<String> fetchUserData(String userId) {
return Future.succeededFuture("User[id=" + userId + ", name=Admin]");
}
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
vertx.deployVerticle(new FutureExampleVerticle());
}
}
In the code above:
- The
greetAsyncmethod takes aStringparameter and returns aFuture<String>encapsulated by aPromise. - The
.compose()method chains asynchronous steps together, automatically passing the output of the previous stage into the input of the next.
Parameters and Return Values in the Event Bus
The Vert.x Event Bus serves as the backbone for distributed systems. When communicating across components, payloads are usually encapsulated within a JsonObject or Buffer.
1. Request-Reply Pattern
Consumer Side:
vertx.eventBus().consumer("user.query", message -> {
// 1. Extract incoming parameters
String userId = (String) message.body();
System.out.println("Received query request for userId = " + userId);
// 2. Process business logic and reply
String responseData = "User details for " + userId;
message.reply(responseData);
});
Producer Side:
String param = "U12345";
vertx.eventBus().request("user.query", param, reply -> {
if (reply.succeeded()) {
String result = (String) reply.result().body();
System.out.println("Successfully received return value: " + result);
} else {
System.err.println("Request failed: " + reply.cause().getMessage());
}
});
2. Passing Complex Parameters Using JsonObject
When multiple parameters need to be sent across, utilizing a JsonObject is the recommended approach:
import io.vertx.core.json.JsonObject;
JsonObject params = new JsonObject()
.put("username", "john_doe")
.put("age", 30)
.put("active", true);
vertx.eventBus().request("user.create", params, reply -> {
if (reply.succeeded()) {
JsonObject response = (JsonObject) reply.result().body();
boolean success = response.getBoolean("success");
System.out.println("Creation result: " + success);
}
});
Conclusion
Handling parameters and return values effectively in Vert.x requires fully embracing the asynchronous mindset:
- Local Method Invocations: Favor designs that return
Future<T>, and leverage.compose()to orchestrate complex asynchronous workflows cleanly. - Inter-Component Communication: Make good use of the
request-replypattern on the Event Bus, combining it withJsonObjectto pass structured parameters for loosely coupled microservices.
Applying these patterns will help you write high-performing, maintainable Vert.x applications.