This is a prerelease version.

View latest

Get started with Vert.x

This tutorial helps you integrate Vert.x with Hazelcast and use Hazelcast for distributed session management and other distributed data structures.

Overview

In this tutorial, you will learn how to:

  • Start with a simple Vert.x Hello World application.

  • Add the vertx-hazelcast module and enable distributed session management.

  • Use the io.vertx.core.shareddata.Counter data structure to implement a unique ID generator.

Prerequisites

  • Java 17 or newer

  • Maven 3.9

  • httpie client

Create a new project

  1. Go to start.vertx.io, change the artifact id to messages, the version to 5.0.0, and generate a new project.

  2. Extract the project and build it using:

$ mvn clean package

Then start the application using:

java -jar target/messages-1.0.0-SNAPSHOT-fat.jar

You should see output similar to the following:

HTTP server started on port 8888
Aug 29, 2024 2:22:38 PM io.vertx.launcher.application.VertxApplication
INFO: Succeeded in deploying verticle

Store data in session

Go to the MainVerticle.java file and replace the contents of the start method with the following:

This tutorial uses 2-space indentation, which is customary for Vert.x projects due to the high number of nested callbacks.
// include::ROOT:example$/vertx/vertxMainVerticleStart.java[] Save for later; keep code sample inline for now
  public void start() {
    // Create a Router
    Router router = router(vertx);

    // Create local SessionStore
    SessionStore store = LocalSessionStore.create(vertx);

    // Use the SessionStore to handle all requests
    router.route()
      .handler(SessionHandler.create(store));

    router.route(HttpMethod.PUT, "/").handler(context -> {
      context.request().bodyHandler(body -> {
        List<String> messages = getMessagesFromSession(context);

        JsonObject json = body.toJsonObject();
        String message = json.getString("message");
        messages.add(message);

        putMessagesToSession(context, messages);

        context.json(
          new JsonObject()
            .put("messages", messages)
        );
      });
    });

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

  private static List<String> getMessagesFromSession(RoutingContext context) {
    String messages = context.session().get("messages");
    if (messages == null) {
      return new ArrayList<>();
    } else {
      return new ArrayList<>(Arrays.asList(messages.split(",")));
    }
  }

  private void putMessagesToSession(RoutingContext context, List<String> messages) {
    context.session().put("messages", String.join(",", messages));
  }

Next, test the server response:

$ http put localhost:8888 message=Hello\ World!
HTTP/1.1 200 OK
content-length: 29
content-type: application/json
set-cookie: vertx-web.session=ed22f77473a7f613c9305431a62832a6; Path=/

{
    "messages": [
        "Hello World!"
    ]
}

Execute another request with the cookie:

$ http put localhost:8888 'Cookie:vertx-web.session=ed22f77473a7f613c9305431a62832a6' message=Hello\ World\ 2!
HTTP/1.1 200 OK
content-length: 46
content-type: application/json

{
    "messages": [
        "Hello World!",
        "Hello World 2!"
    ]
}

Distributed sessions

Let’s modify the code, so we can start multiple instances easily — the application will start on the defined port and, if the port is unavailable, it will search for another port:

Add the following method to the MainVerticle.java class:

private int findFreePort(int from) {
    for (int port = from; port < from + 100; port++) {
      try {
        new ServerSocket(port).close();
        return port;
      } catch (IOException e) {
        // port not available, try next
      }
    }
    throw new RuntimeException("Could not find an available port");
  }

And then use it in the start method:

...
int port = findFreePort(8888);

// Create the HTTP server
vertx.createHttpServer()
  // Handle every request using the router
  .requestHandler(router)
  // Start listening
  .listen(port)
...

Now, we can start two instances:

$ java -jar target/vertx-hz-1.0.0-SNAPSHOT-fat.jar
HTTP server started on port 8888
Aug 30, 2024 9:09:44 AM io.vertx.launcher.application.VertxApplication
INFO: Succeeded in deploying verticle

...

$ java -jar target/vertx-hz-1.0.0-SNAPSHOT-fat.jar
HTTP server started on port 8889
Aug 30, 2024 9:09:47 AM io.vertx.launcher.application.VertxApplication
INFO: Succeeded in deploying verticle

We can see the session is not shared between the instances. Here is the request to the first instance:

$ http PUT localhost:8888 message="Hello world"
HTTP/1.1 200 OK
content-length: 28
content-type: application/json
set-cookie: vertx-web.session=00f219c166ca50727d23eaaf9fe54229; Path=/

{
    "messages": [
        "Hello world"
    ]
}

And here is the request to the 2nd instance. Notice the different port and that we use the cookie we received, but the data does not contain the previous message.

$ http PUT localhost:8889 message="Hello world 2" 'Cookie: vertx-web.session=00f219c166ca50727d23eaaf9fe54229'
HTTP/1.1 200 OK
content-length: 30
content-type: application/json
set-cookie: vertx-web.session=a1486c5ed6416972fdc356e4d91d2397; Path=/

{
    "messages": [
        "Hello world 2"
    ]
}

We will fix that by using a Hazelcast Cluster Manager. This resides in the io.vertx:vertx-hazelcast module, which is maintained by the Vert.x team with contributions from Hazelcast and is based on Hazelcast Community Edition.

Change the following part of the start method:

// Create local SessionStore
SessionStore store = LocalSessionStore.create(vertx);

to the following:

// Create clustered SessionStore
SessionStore store = ClusteredSessionStore.create(vertx);

From now on, we will start the application with the -server parameter, which tells Vert.x to look for a cluster manager implementation.

We also need to provide a Hazelcast configuration file, and create a file cluster.xml in the src/main/resources directory:

<?xml version="1.0" encoding="UTF-8"?>

<hazelcast xmlns="http://www.hazelcast.com/schema/config"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.hazelcast.com/schema/config
           https://www.hazelcast.com/schema/config/hazelcast-config-5.5.xsd">

  <network>
    <join>
      <multicast enabled="true"/>
    </join>
  </network>

  <multimap name="__vertx.subs">
    <backup-count>1</backup-count>
    <value-collection-type>SET</value-collection-type>
  </multimap>

  <map name="__vertx.haInfo">
    <backup-count>1</backup-count>
  </map>

  <map name="__vertx.nodeInfo">
    <backup-count>1</backup-count>
  </map>

Now rebuild the project and start the application. You will see more verbose output as Hazelcast prints its own startup logs:

$ java -jar target/vertx-hz-1.0.0-SNAPSHOT-fat.jar -cluster
...
HTTP server started on port 8888
...
Members {size:2, ver:2} [
	Member [192.168.0.10]:5701 - e29f0362-f9a9-4708-b6e5-1a6067b5aa39 this
	Member [192.168.0.10]:5702 - 74014573-a18a-44f2-9ca7-fd90b70dcb43
]
...

And:

$ java -jar target/vertx-hz-1.0.0-SNAPSHOT-fat.jar -cluster
...
HTTP server started on port 8889
...
Members {size:2, ver:2} [
	Member [192.168.0.10]:5701 - e29f0362-f9a9-4708-b6e5-1a6067b5aa39
	Member [192.168.0.10]:5702 - 74014573-a18a-44f2-9ca7-fd90b70dcb43 this
]
...

Putting two messages into different instances while using the same cookie, we see that the session is shared between the instances.

$ http PUT localhost:8888 message="Hello world"
HTTP/1.1 200 OK
content-length: 31
content-type: application/json
set-cookie: vertx-web.session=1ab47cb96731123135f25ec7b67efd64; Path=/

{
    "messages": [
        "",
        "Hello world"
    ]
}
$ http PUT localhost:8889 message="Hello world 2" 'Cookie: vertx-web.session=674806546c690674962f279670abefcf'
HTTP/1.1 200 OK
content-length: 44
content-type: application/json

{
    "messages": [
        "Hello world",
        "Hello world 2"
    ]
}

Using Counter

Next, we’ll use Hazelcast’s distributed counter functionality to generate unique, incrementing IDs across a distributed system. The counter is maintained across all nodes in the Hazelcast cluster, ensuring each request gets a unique ID even when multiple servers are handling requests.

Replace this part of the code at the end of the start() method:

context.json(
  new JsonObject()
    .put("messages", messages)
);

with the following:

context.vertx()
  .sharedData()
  .getCounter("requestId")
  .onSuccess(counter -> {
    counter.incrementAndGet()
      .onSuccess(requestId -> {
        context.json(
          new JsonObject()
            .put("requestId", requestId)
            .put("messages", messages)
        );
      });
  });

Now, when you try the application, you can see the response contains an additional field named requestId and its value increments for every request.

$ http PUT localhost:8888 message="Hello world"
HTTP/1.1 200 OK
content-length: 42
content-type: application/json
set-cookie: vertx-web.session=d9fb4cada5c0fc625089a38f3de13e3c; Path=/

{
    "messages": [
        "Hello world"
    ],
    "requestId": 1
}

Summary

In this tutorial, you learned how to add the vertx-hazelcast module and enable distributed session management, as well as how to use the io.vertx.core.shareddata.Counter data structure to implement a unique id generator.