Vert.x

Reactive application development toolkit on the JVM. Achieves high performance and high concurrency through event-driven, non-blocking architecture.

JavaWeb FrameworkReactiveAsynchronousHigh ConcurrencyEvent-drivenNetty

GitHub Overview

eclipse-vertx/vert.x

Vert.x is a tool-kit for building reactive applications on the JVM

Stars14,521
Watchers521
Forks2,091
Created:June 17, 2011
Language:Java
License:Other

Topics

concurrencyevent-loophigh-performancehttp2javajvmnettynionon-blockingreactivevertx

Star History

eclipse-vertx/vert.x Star History
Data as of: 7/17/2025, 10:32 AM

Web Framework

Vert.x

Overview

Vert.x is a reactive application building toolkit known as "the Java framework for busy servers". It adopts an event-driven, single-threaded, non-blocking I/O model, enabling high performance handling of numerous concurrent connections with a small number of kernel threads. It's an asynchronous network application framework based on Netty.

Details

Vert.x is an open-source project developed and maintained by the Eclipse Foundation. As of 2024, active development continues, and it consistently ranks in the top 5 of TechEmpower Web Framework Benchmarks, demonstrating superior performance over renowned frameworks like Express.js, ASP.NET Core, and Spring Boot.

Key architectural features:

  • Reactive Non-blocking: High concurrency processing through event loop model
  • Verticle Architecture: Modular design enabling independent deployment and scaling
  • Event Bus: Asynchronous messaging mechanism between Verticles
  • Polyglot Support: Support for Java, JavaScript, Groovy, Ruby, Scala, Kotlin
  • Toolkit Approach: Ability to select and combine only necessary functions

Vert.x is designed as a "toolkit" rather than a "framework", imposing no strong constraints on application structure and allowing developers to freely select and combine necessary modules and clients.

Advantages and Disadvantages

Advantages

  • Exceptional Performance: Achieves significant speed improvements compared to Spring Boot in latest benchmarks
  • High Concurrency Handling: Efficiently processes numerous concurrent requests with few threads
  • Resource Efficiency: Lower CPU and memory consumption compared to traditional blocking I/O-based frameworks
  • Polyglot: Multi-JVM language support enables easy integration with existing codebases
  • Modular Design: Lightweight and flexible architecture allowing selection of only necessary features
  • Comprehensive Ecosystem: Complete stack for Web APIs, databases, messaging, and cloud compatibility
  • Low Learning Curve: Relatively accessible design for new developers
  • Container Optimization: Excellent performance in constrained environments like VMs and containers

Disadvantages

  • Code Complexity: Non-blocking code is more difficult to read, write, and debug
  • Callback Hell: Risk of readability degradation due to numerous nested callbacks
  • Blocking Operation Pitfalls: Risk of accidentally including blocking code
  • Overkill for Simple Apps: Unsuitable for CRUD applications that don't require high concurrency or low latency
  • Reactive Programming Learning Curve: Requires specialized knowledge for concept understanding and blocking operation avoidance
  • Debugging Complexity: Debugging asynchronous processing can become complex

Reference Links

Code Examples

Hello World

// HelloWorldVerticle.java
package com.example;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerResponse;

public class HelloWorldVerticle extends AbstractVerticle {

    @Override
    public void start(Promise<Void> startPromise) throws Exception {
        HttpServer server = vertx.createHttpServer();

        server.requestHandler(request -> {
            HttpServerResponse response = request.response();
            response
                .putHeader("content-type", "text/plain; charset=utf-8")
                .end("Hello World from Vert.x!");
        });

        server.listen(8080, result -> {
            if (result.succeeded()) {
                System.out.println("Server started on port 8080");
                startPromise.complete();
            } else {
                System.out.println("Failed to start server: " + result.cause());
                startPromise.fail(result.cause());
            }
        });
    }

    public static void main(String[] args) {
        Vertx vertx = Vertx.vertx();
        vertx.deployVerticle(new HelloWorldVerticle());
    }
}

RESTful API and Routing

// UserApiVerticle.java
package com.example;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

public class UserApiVerticle extends AbstractVerticle {
    
    private Map<Long, JsonObject> users = new HashMap<>();
    private AtomicLong idCounter = new AtomicLong(1);

    @Override
    public void start(Promise<Void> startPromise) throws Exception {
        Router router = Router.router(vertx);
        
        // Add body parser
        router.route().handler(BodyHandler.create());
        
        // Initialize sample data
        initializeData();
        
        // Route definitions
        router.get("/").handler(this::handleRoot);
        router.get("/api/users").handler(this::getAllUsers);
        router.get("/api/users/:id").handler(this::getUser);
        router.post("/api/users").handler(this::createUser);
        router.put("/api/users/:id").handler(this::updateUser);
        router.delete("/api/users/:id").handler(this::deleteUser);

        vertx.createHttpServer()
            .requestHandler(router)
            .listen(8080, result -> {
                if (result.succeeded()) {
                    System.out.println("User API Server started on port 8080");
                    startPromise.complete();
                } else {
                    startPromise.fail(result.cause());
                }
            });
    }

    private void initializeData() {
        users.put(1L, new JsonObject()
            .put("id", 1)
            .put("name", "John Doe")
            .put("email", "[email protected]")
            .put("age", 30));
        users.put(2L, new JsonObject()
            .put("id", 2)
            .put("name", "Jane Smith")
            .put("email", "[email protected]")
            .put("age", 25));
        idCounter.set(3);
    }

    private void handleRoot(RoutingContext context) {
        context.response()
            .putHeader("content-type", "application/json; charset=utf-8")
            .end(new JsonObject()
                .put("message", "Welcome to User API")
                .put("endpoints", new JsonArray()
                    .add("GET /api/users")
                    .add("GET /api/users/:id")
                    .add("POST /api/users")
                    .add("PUT /api/users/:id")
                    .add("DELETE /api/users/:id"))
                .encode());
    }

    private void getAllUsers(RoutingContext context) {
        JsonArray userArray = new JsonArray();
        users.values().forEach(userArray::add);
        
        context.response()
            .putHeader("content-type", "application/json; charset=utf-8")
            .end(userArray.encode());
    }

    private void getUser(RoutingContext context) {
        String idParam = context.request().getParam("id");
        try {
            Long id = Long.parseLong(idParam);
            JsonObject user = users.get(id);
            
            if (user != null) {
                context.response()
                    .putHeader("content-type", "application/json; charset=utf-8")
                    .end(user.encode());
            } else {
                context.response()
                    .setStatusCode(404)
                    .putHeader("content-type", "application/json; charset=utf-8")
                    .end(new JsonObject()
                        .put("error", "User not found")
                        .encode());
            }
        } catch (NumberFormatException e) {
            context.response()
                .setStatusCode(400)
                .putHeader("content-type", "application/json; charset=utf-8")
                .end(new JsonObject()
                    .put("error", "Invalid user ID")
                    .encode());
        }
    }

    private void createUser(RoutingContext context) {
        JsonObject body = context.getBodyAsJson();
        
        if (body == null || !isValidUser(body)) {
            context.response()
                .setStatusCode(400)
                .putHeader("content-type", "application/json; charset=utf-8")
                .end(new JsonObject()
                    .put("error", "Invalid user data")
                    .encode());
            return;
        }

        Long id = idCounter.getAndIncrement();
        JsonObject user = new JsonObject()
            .put("id", id)
            .put("name", body.getString("name"))
            .put("email", body.getString("email"))
            .put("age", body.getInteger("age"));
        
        users.put(id, user);
        
        context.response()
            .setStatusCode(201)
            .putHeader("content-type", "application/json; charset=utf-8")
            .end(user.encode());
    }

    private void updateUser(RoutingContext context) {
        String idParam = context.request().getParam("id");
        JsonObject body = context.getBodyAsJson();
        
        if (body == null || !isValidUser(body)) {
            context.response()
                .setStatusCode(400)
                .putHeader("content-type", "application/json; charset=utf-8")
                .end(new JsonObject()
                    .put("error", "Invalid user data")
                    .encode());
            return;
        }

        try {
            Long id = Long.parseLong(idParam);
            if (users.containsKey(id)) {
                JsonObject user = new JsonObject()
                    .put("id", id)
                    .put("name", body.getString("name"))
                    .put("email", body.getString("email"))
                    .put("age", body.getInteger("age"));
                
                users.put(id, user);
                
                context.response()
                    .putHeader("content-type", "application/json; charset=utf-8")
                    .end(user.encode());
            } else {
                context.response()
                    .setStatusCode(404)
                    .putHeader("content-type", "application/json; charset=utf-8")
                    .end(new JsonObject()
                        .put("error", "User not found")
                        .encode());
            }
        } catch (NumberFormatException e) {
            context.response()
                .setStatusCode(400)
                .putHeader("content-type", "application/json; charset=utf-8")
                .end(new JsonObject()
                    .put("error", "Invalid user ID")
                    .encode());
        }
    }

    private void deleteUser(RoutingContext context) {
        String idParam = context.request().getParam("id");
        try {
            Long id = Long.parseLong(idParam);
            if (users.remove(id) != null) {
                context.response()
                    .setStatusCode(204)
                    .end();
            } else {
                context.response()
                    .setStatusCode(404)
                    .putHeader("content-type", "application/json; charset=utf-8")
                    .end(new JsonObject()
                        .put("error", "User not found")
                        .encode());
            }
        } catch (NumberFormatException e) {
            context.response()
                .setStatusCode(400)
                .putHeader("content-type", "application/json; charset=utf-8")
                .end(new JsonObject()
                    .put("error", "Invalid user ID")
                    .encode());
        }
    }

    private boolean isValidUser(JsonObject user) {
        return user.getString("name") != null && !user.getString("name").trim().isEmpty() &&
               user.getString("email") != null && !user.getString("email").trim().isEmpty() &&
               user.getInteger("age") != null && user.getInteger("age") > 0;
    }
}

Event Bus and Inter-Verticle Communication

// EventBusExampleVerticle.java
package com.example;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.Message;
import io.vertx.core.json.JsonObject;

public class EventBusExampleVerticle extends AbstractVerticle {
    
    @Override
    public void start(Promise<Void> startPromise) throws Exception {
        EventBus eventBus = vertx.eventBus();
        
        // Register message consumers
        eventBus.consumer("user.service", this::handleUserService);
        eventBus.consumer("notification.service", this::handleNotification);
        
        // Start HTTP server
        vertx.createHttpServer()
            .requestHandler(request -> {
                String path = request.path();
                
                if (path.equals("/send-message")) {
                    // Send message via event bus
                    JsonObject message = new JsonObject()
                        .put("action", "get_user")
                        .put("userId", 123);
                    
                    eventBus.request("user.service", message, reply -> {
                        if (reply.succeeded()) {
                            request.response()
                                .putHeader("content-type", "application/json")
                                .end(reply.result().body().toString());
                        } else {
                            request.response()
                                .setStatusCode(500)
                                .end("Error: " + reply.cause().getMessage());
                        }
                    });
                } else if (path.equals("/publish")) {
                    // Publish/Subscribe model
                    JsonObject notification = new JsonObject()
                        .put("type", "info")
                        .put("message", "System notification");
                    
                    eventBus.publish("notification.service", notification);
                    
                    request.response()
                        .putHeader("content-type", "text/plain")
                        .end("Notification sent");
                } else {
                    request.response()
                        .putHeader("content-type", "text/plain")
                        .end("Event Bus examples: /send-message, /publish");
                }
            })
            .listen(8080, result -> {
                if (result.succeeded()) {
                    System.out.println("EventBus server started on port 8080");
                    startPromise.complete();
                } else {
                    startPromise.fail(result.cause());
                }
            });
    }
    
    private void handleUserService(Message<JsonObject> message) {
        JsonObject body = message.body();
        String action = body.getString("action");
        
        if ("get_user".equals(action)) {
            Integer userId = body.getInteger("userId");
            
            // Simulate user data retrieval
            JsonObject user = new JsonObject()
                .put("id", userId)
                .put("name", "User" + userId)
                .put("email", "user" + userId + "@example.com")
                .put("timestamp", System.currentTimeMillis());
            
            message.reply(user);
        } else {
            message.fail(400, "Unknown action: " + action);
        }
    }
    
    private void handleNotification(Message<JsonObject> message) {
        JsonObject notification = message.body();
        String type = notification.getString("type");
        String msg = notification.getString("message");
        
        // Simulate notification processing
        System.out.println("[" + type.toUpperCase() + "] " + msg);
        
        // Forward to other services
        if ("info".equals(type)) {
            vertx.setTimer(1000, id -> {
                System.out.println("Notification processing completed: " + msg);
            });
        }
    }
}

WebSocket Support

// WebSocketServerVerticle.java
package com.example;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.ServerWebSocket;
import io.vertx.core.json.JsonObject;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class WebSocketServerVerticle extends AbstractVerticle {
    
    private Set<ServerWebSocket> connectedSockets = ConcurrentHashMap.newKeySet();
    
    @Override
    public void start(Promise<Void> startPromise) throws Exception {
        HttpServer server = vertx.createHttpServer();
        
        // WebSocket handler
        server.webSocketHandler(websocket -> {
            System.out.println("WebSocket connected: " + websocket.textHandlerID());
            connectedSockets.add(websocket);
            
            // Connection notification
            JsonObject welcomeMsg = new JsonObject()
                .put("type", "welcome")
                .put("message", "Connected to WebSocket server")
                .put("clientId", websocket.textHandlerID());
            websocket.writeTextMessage(welcomeMsg.encode());
            
            // Message handler
            websocket.textMessageHandler(message -> {
                try {
                    JsonObject msgObj = new JsonObject(message);
                    handleWebSocketMessage(websocket, msgObj);
                } catch (Exception e) {
                    JsonObject errorMsg = new JsonObject()
                        .put("type", "error")
                        .put("message", "Invalid JSON message");
                    websocket.writeTextMessage(errorMsg.encode());
                }
            });
            
            // Close handler
            websocket.closeHandler(v -> {
                System.out.println("WebSocket disconnected: " + websocket.textHandlerID());
                connectedSockets.remove(websocket);
                
                // Notify other clients of disconnection
                JsonObject disconnectMsg = new JsonObject()
                    .put("type", "user_disconnected")
                    .put("clientId", websocket.textHandlerID());
                broadcastMessage(disconnectMsg, websocket);
            });
            
            // Exception handler
            websocket.exceptionHandler(throwable -> {
                System.err.println("WebSocket error: " + throwable.getMessage());
                connectedSockets.remove(websocket);
            });
        });
        
        // Periodic heartbeat
        vertx.setPeriodic(30000, id -> {
            JsonObject heartbeat = new JsonObject()
                .put("type", "heartbeat")
                .put("timestamp", System.currentTimeMillis())
                .put("connectedClients", connectedSockets.size());
            broadcastMessage(heartbeat, null);
        });
        
        server.listen(8080, result -> {
            if (result.succeeded()) {
                System.out.println("WebSocket server started on port 8080");
                startPromise.complete();
            } else {
                startPromise.fail(result.cause());
            }
        });
    }
    
    private void handleWebSocketMessage(ServerWebSocket sender, JsonObject message) {
        String type = message.getString("type");
        
        switch (type) {
            case "chat":
                // Broadcast chat message
                JsonObject chatMsg = new JsonObject()
                    .put("type", "chat")
                    .put("from", sender.textHandlerID())
                    .put("message", message.getString("message"))
                    .put("timestamp", System.currentTimeMillis());
                broadcastMessage(chatMsg, sender);
                break;
                
            case "ping":
                // Respond to ping with pong
                JsonObject pongMsg = new JsonObject()
                    .put("type", "pong")
                    .put("timestamp", System.currentTimeMillis());
                sender.writeTextMessage(pongMsg.encode());
                break;
                
            case "get_users":
                // Get list of connected users
                JsonObject usersMsg = new JsonObject()
                    .put("type", "users_list")
                    .put("count", connectedSockets.size())
                    .put("users", connectedSockets.stream()
                        .map(ServerWebSocket::textHandlerID)
                        .toArray());
                sender.writeTextMessage(usersMsg.encode());
                break;
                
            default:
                JsonObject unknownMsg = new JsonObject()
                    .put("type", "error")
                    .put("message", "Unknown message type: " + type);
                sender.writeTextMessage(unknownMsg.encode());
        }
    }
    
    private void broadcastMessage(JsonObject message, ServerWebSocket excludeSocket) {
        String messageStr = message.encode();
        connectedSockets.forEach(socket -> {
            if (socket != excludeSocket && !socket.isClosed()) {
                socket.writeTextMessage(messageStr);
            }
        });
    }
}

Asynchronous Database Operations

// DatabaseVerticle.java
package com.example;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.jdbc.JDBCClient;
import io.vertx.ext.sql.SQLConnection;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;

public class DatabaseVerticle extends AbstractVerticle {
    
    private JDBCClient jdbcClient;
    
    @Override
    public void start(Promise<Void> startPromise) throws Exception {
        // Database connection configuration
        JsonObject config = new JsonObject()
            .put("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1")
            .put("driver_class", "org.h2.Driver");
        
        jdbcClient = JDBCClient.createShared(vertx, config);
        
        // Initialize database
        initializeDatabase().onComplete(dbResult -> {
            if (dbResult.succeeded()) {
                setupRouter(startPromise);
            } else {
                startPromise.fail(dbResult.cause());
            }
        });
    }
    
    private Promise<Void> initializeDatabase() {
        Promise<Void> promise = Promise.promise();
        
        String createTable = """
            CREATE TABLE IF NOT EXISTS users (
                id INT AUTO_INCREMENT PRIMARY KEY,
                name VARCHAR(255) NOT NULL,
                email VARCHAR(255) UNIQUE NOT NULL,
                age INT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
            """;
        
        jdbcClient.getConnection(connResult -> {
            if (connResult.succeeded()) {
                SQLConnection connection = connResult.result();
                
                connection.execute(createTable, createResult -> {
                    if (createResult.succeeded()) {
                        // Insert sample data
                        String insertData = """
                            INSERT INTO users (name, email, age) VALUES
                            ('John Doe', '[email protected]', 30),
                            ('Jane Smith', '[email protected]', 25),
                            ('Bob Johnson', '[email protected]', 35)
                            """;
                        
                        connection.execute(insertData, insertResult -> {
                            connection.close();
                            if (insertResult.succeeded()) {
                                promise.complete();
                            } else {
                                promise.fail(insertResult.cause());
                            }
                        });
                    } else {
                        connection.close();
                        promise.fail(createResult.cause());
                    }
                });
            } else {
                promise.fail(connResult.cause());
            }
        });
        
        return promise;
    }
    
    private void setupRouter(Promise<Void> startPromise) {
        Router router = Router.router(vertx);
        router.route().handler(BodyHandler.create());
        
        router.get("/api/users").handler(this::getAllUsers);
        router.get("/api/users/:id").handler(this::getUser);
        router.post("/api/users").handler(this::createUser);
        router.put("/api/users/:id").handler(this::updateUser);
        router.delete("/api/users/:id").handler(this::deleteUser);
        
        vertx.createHttpServer()
            .requestHandler(router)
            .listen(8080, result -> {
                if (result.succeeded()) {
                    System.out.println("Database API server started on port 8080");
                    startPromise.complete();
                } else {
                    startPromise.fail(result.cause());
                }
            });
    }
    
    private void getAllUsers(RoutingContext context) {
        jdbcClient.getConnection(connResult -> {
            if (connResult.succeeded()) {
                SQLConnection connection = connResult.result();
                
                connection.query("SELECT * FROM users ORDER BY id", queryResult -> {
                    connection.close();
                    
                    if (queryResult.succeeded()) {
                        JsonArray users = new JsonArray();
                        queryResult.result().getRows().forEach(row -> {
                            users.add(new JsonObject()
                                .put("id", row.getInteger("id"))
                                .put("name", row.getString("name"))
                                .put("email", row.getString("email"))
                                .put("age", row.getInteger("age"))
                                .put("created_at", row.getString("created_at")));
                        });
                        
                        context.response()
                            .putHeader("content-type", "application/json")
                            .end(users.encode());
                    } else {
                        handleDatabaseError(context, queryResult.cause());
                    }
                });
            } else {
                handleDatabaseError(context, connResult.cause());
            }
        });
    }
    
    private void getUser(RoutingContext context) {
        // Implementation similar to getAllUsers pattern
    }
    
    private void createUser(RoutingContext context) {
        // Implementation similar to getAllUsers pattern
    }
    
    private void updateUser(RoutingContext context) {
        // Implementation similar to getAllUsers pattern
    }
    
    private void deleteUser(RoutingContext context) {
        // Implementation similar to getAllUsers pattern
    }
    
    private boolean isValidUser(JsonObject user) {
        return user.getString("name") != null && !user.getString("name").trim().isEmpty() &&
               user.getString("email") != null && !user.getString("email").trim().isEmpty() &&
               user.getInteger("age") != null && user.getInteger("age") > 0;
    }
    
    private void handleDatabaseError(RoutingContext context, Throwable error) {
        System.err.println("Database error: " + error.getMessage());
        context.response()
            .setStatusCode(500)
            .putHeader("content-type", "application/json")
            .end(new JsonObject()
                .put("error", "Internal server error")
                .encode());
    }
}

Vert.x Unit Testing

// VerticleTest.java
package com.example;

import io.vertx.core.Vertx;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(VertxUnitRunner.class)
public class VerticleTest {
    
    private Vertx vertx;
    private HttpClient client;
    
    @Before
    public void setUp(TestContext context) {
        vertx = Vertx.vertx();
        
        // Deploy test Verticle
        Async async = context.async();
        vertx.deployVerticle(UserApiVerticle.class.getName(), context.asyncAssertSuccess(id -> {
            async.complete();
        }));
        
        client = vertx.createHttpClient();
    }
    
    @After
    public void tearDown(TestContext context) {
        vertx.close(context.asyncAssertSuccess());
    }
    
    @Test
    public void testGetAllUsers(TestContext context) {
        Async async = context.async();
        
        client.getNow(8080, "localhost", "/api/users", response -> {
            context.assertEquals(200, response.statusCode());
            context.assertEquals("application/json; charset=utf-8", 
                response.getHeader("content-type"));
            
            response.bodyHandler(body -> {
                context.assertTrue(body.toString().contains("John Doe"));
                async.complete();
            });
        });
    }
    
    @Test
    public void testCreateUser(TestContext context) {
        Async async = context.async();
        
        String userData = """
            {
                "name": "Test User",
                "email": "[email protected]",
                "age": 28
            }
            """;
        
        client.post(8080, "localhost", "/api/users")
            .putHeader("content-type", "application/json")
            .handler(response -> {
                context.assertEquals(201, response.statusCode());
                
                response.bodyHandler(body -> {
                    context.assertTrue(body.toString().contains("Test User"));
                    async.complete();
                });
            })
            .end(userData);
    }
    
    @Test
    public void testGetUserNotFound(TestContext context) {
        Async async = context.async();
        
        client.getNow(8080, "localhost", "/api/users/999", response -> {
            context.assertEquals(404, response.statusCode());
            async.complete();
        });
    }
}