Lightweight RFC 6455 WebSocket support for GuicedEE applications using Vert.x 5. Connections are call-scoped, messages are dispatched through an action-based receiver SPI, and group membership is managed via the Vert.x EventBus. Builds on top of web for HTTP server plumbing.
Built on Vert.x · Google Guice · Mutiny · JPMS module com.guicedee.vertx.sockets · Java 25+
<dependency>
<groupId>com.guicedee</groupId>
<artifactId>websockets</artifactId>
</dependency>Gradle (Kotlin DSL)
implementation("com.guicedee:websockets:2.0.0-SNAPSHOT")- Auto-configured WebSocket server —
VertxSocketHttpWebSocketConfiguratorregisters the WebSocket handler on the Vert.x HTTP server automatically via SPI - Call-scoped connections — each WebSocket connection gets its own Guice
@CallScopewith access toCallScopeProperties,ServerWebSocket, andIGuicedWebSocket - Action-based message routing — inbound JSON messages are deserialized to
WebSocketMessageReceiverand dispatched toIWebSocketMessageReceiverhandlers by action name - Group management — connections join/leave named groups; broadcast messages are delivered to all group members via the Vert.x EventBus
- SPI-driven lifecycle hooks —
GuicedWebSocketOnAddToGroup,GuicedWebSocketOnRemoveFromGroup, andGuicedWebSocketOnPublishlet you intercept group operations - Reactive message processing —
receiveMessage()returnsUni<Void>for non-blocking composition - Per-message compression — RFC 7692 WebSocket compression enabled by default
- JSpecify nullability annotations —
@NonNull/@Nullableon public API for clarity and safety - Configurable server options — injectable
WebSocketServerOptionssingleton for compression, frame sizes, and connection limits
Step 1 — Implement a message receiver:
public class ChatReceiver implements IWebSocketMessageReceiver<Void, ChatReceiver> {
@Override
public Set<String> messageNames() {
return Set.of("chat");
}
@Override
public Uni<Void> receiveMessage(WebSocketMessageReceiver<?> message) {
String text = (String) message.getData().get("text");
IGuicedWebSocket ws = IGuiceContext.get(IGuicedWebSocket.class);
ws.broadcastMessage("chat:lobby", text);
return Uni.createFrom().voidItem();
}
}Step 2 — Register via JPMS:
module my.app {
requires com.guicedee.vertx.sockets;
provides com.guicedee.client.services.websocket.IWebSocketMessageReceiver
with my.app.ChatReceiver;
}Step 3 — Bootstrap GuicedEE (WebSocket server starts automatically):
IGuiceContext.registerModuleForScanning.add("my.app");
IGuiceContext.instance();
// WebSocket server is now accepting connections on the HTTP portClients connect to ws://localhost:8080 and send JSON:
{ "action": "chat", "data": { "text": "Hello, world!" } }Startup
IGuiceContext.instance()
└─ VertxWebServerPostStartup (from web module — creates HTTP server)
└─ VertxSocketHttpWebSocketConfigurator
├─ HttpServerOptions builder (compression, frame sizes)
├─ HttpServer builder (registers webSocketHandler)
└─ Router builder (no-op — WebSockets bypass the router)
Client connects (ws://...)
→ Vert.x HttpServer.webSocketHandler()
→ CallScoper enters @CallScope
→ CallScopeProperties initialized (RequestContextId = textHandlerID)
→ Connection added to "Everyone" group
→ Per-connection EventBus consumer registered (address = textHandlerID)
→ textMessageHandler installed
→ JSON → WebSocketMessageReceiver deserialization
→ IGuicedWebSocket.getMessagesListeners() lookup by action
→ IWebSocketMessageReceiver.receiveMessage() → Uni<Void>
→ closeHandler / exceptionHandler
→ Connection removed from all groups
→ CallScopeProperties cleaned up
Client sends JSON: { "action": "chat", "data": { "text": "hi" } }
→ textMessageHandler
→ processMessageInContext() ← re-enters CallScope for this connection
→ GuicedWebSocket.receiveMessage()
→ ObjectMapper.readValue() → WebSocketMessageReceiver
→ Lookup action "chat" in messageListeners
→ ChatReceiver.receiveMessage() ← your handler
→ Uni subscribed, errors logged
GuicedWebSocket.broadcastMessage("chat:lobby", "Hello")
→ GuicedWebSocketOnPublish SPI check ← custom publish hook (if registered)
→ Fallback: iterate groupSockets["chat:lobby"]
→ writeTextMessage() to each ServerWebSocket
Inbound messages are JSON-deserialized into WebSocketMessageReceiver:
| Field | Type | Required | Purpose |
|---|---|---|---|
action |
String |
✅ | Routes to the matching IWebSocketMessageReceiver |
data |
Map<String, Object> |
❌ | Arbitrary key/value payload |
broadcastGroup |
String |
❌ | Set automatically to the connection's RequestContextId |
dataService |
String |
❌ | Optional service discriminator |
webSocketSessionId |
String |
❌ | Optional client-set session identifier |
Unknown JSON fields are captured via @JsonAnySetter into the data map.
{ "action": "join", "data": { "room": "lobby" } }{ "action": "send", "data": { "text": "Hello!", "to": "user-42" } }Every connection is automatically added to the Everyone group and a per-connection group (keyed by textHandlerID).
IGuicedWebSocket ws = IGuiceContext.get(IGuicedWebSocket.class);
// Join a named group
ws.addToGroup("chat:lobby");
// Leave a named group
ws.removeFromGroup("chat:lobby");// Async — broadcasts via iterating group sockets
ws.broadcastMessage("chat:lobby", "Hello everyone!");
// Send to this connection only (via EventBus)
ws.broadcastMessage("Private message for you");
// Sync — writes directly to the current ServerWebSocket
ws.broadcastMessageSync("chat:lobby", "Immediate message");Each group has:
- An EventBus consumer that forwards messages to all group members
- A socket list (
CopyOnWriteArrayList<ServerWebSocket>) tracking connected clients
When a connection closes or errors, it is automatically removed from all groups.
All SPIs are discovered via ServiceLoader. Register implementations with JPMS provides...with or META-INF/services.
The primary extension point — handles inbound messages routed by action name:
public class EchoReceiver implements IWebSocketMessageReceiver<Void, EchoReceiver> {
@Override
public Set<String> messageNames() {
return Set.of("echo");
}
@Override
public Uni<Void> receiveMessage(WebSocketMessageReceiver<?> message) {
String text = (String) message.getData().get("text");
IGuicedWebSocket ws = IGuiceContext.get(IGuicedWebSocket.class);
ws.broadcastMessage(message.getBroadcastGroup(), text);
return Uni.createFrom().voidItem();
}
}Intercepts group join operations. Return true from the CompletableFuture to indicate the join was handled (skips the default group logic):
public class AuditGroupJoin implements GuicedWebSocketOnAddToGroup<AuditGroupJoin> {
@Override
public CompletableFuture<Boolean> onAddToGroup(String groupName) {
auditService.logGroupJoin(groupName);
return CompletableFuture.completedFuture(false); // proceed with default
}
}Intercepts group leave operations. Same CompletableFuture<Boolean> contract as add:
public class AuditGroupLeave implements GuicedWebSocketOnRemoveFromGroup<AuditGroupLeave> {
@Override
public CompletableFuture<Boolean> onRemoveFromGroup(String groupName) {
auditService.logGroupLeave(groupName);
return CompletableFuture.completedFuture(false);
}
}Intercepts broadcast operations. Return true to indicate the publish was handled (skips the default broadcast logic):
public class FilteredPublish implements GuicedWebSocketOnPublish<FilteredPublish> {
@Override
public boolean publish(String groupName, String message) throws Exception {
if (containsProfanity(message)) {
return true; // swallow the message
}
return false; // proceed with default broadcast
}
}| SPI | Purpose | Return |
|---|---|---|
IWebSocketMessageReceiver |
Handle inbound messages by action name | Uni<R> |
GuicedWebSocketOnAddToGroup |
Intercept group join operations | CompletableFuture<Boolean> |
GuicedWebSocketOnRemoveFromGroup |
Intercept group leave operations | CompletableFuture<Boolean> |
GuicedWebSocketOnPublish |
Intercept broadcast operations | boolean |
IOnCallScopeEnter |
Hook into call scope entry | — |
IOnCallScopeExit |
Hook into call scope exit | — |
An injectable @Singleton that controls WebSocket server behavior. Override defaults by injecting and configuring before startup, or by providing a custom Guice binding:
| Property | Default | Purpose |
|---|---|---|
perMessageCompressionSupported |
true |
Enable RFC 7692 per-message compression |
compressionLevel |
9 |
Compression level (0–9) |
maxFrameSize |
65536 |
Max WebSocket frame size in bytes |
maxChunkSize |
65536 |
Max HTTP chunk size in bytes |
maxFormAttributeSize |
65536 |
Max form attribute size in bytes |
registerWebSocketWriteHandlers |
true |
Register write handlers for backpressure |
idleTimeoutSeconds |
300 |
Connection idle timeout in seconds |
maxGroupSize |
10000 |
Max WebSocket connections per group |
Options are validated at startup — invalid values throw IllegalArgumentException.
public class MyWebSocketConfig extends AbstractModule implements IGuiceModule<MyWebSocketConfig> {
@Override
protected void configure() {
bind(WebSocketServerOptions.class).toInstance(new WebSocketServerOptions() {{
setCompressionLevel(6);
setMaxFrameSize(131072);
setIdleTimeoutSeconds(600);
}});
}
}WebSocket connections run inside Guice's @CallScope. The following are available for injection within a WebSocket context:
| Type | Scope | Purpose |
|---|---|---|
IGuicedWebSocket |
@CallScope |
Group management and broadcasting |
ServerWebSocket |
@CallScope |
The raw Vert.x WebSocket connection |
CallScopeProperties |
@CallScope |
Per-connection properties (RequestContextId, ServerWebSocket, etc.) |
Vertx |
@Singleton |
The shared Vert.x instance |
WebSocketServerOptions |
@Singleton |
Server configuration |
VertxWebSocketsModule automatically configures:
ServerWebSocket→ provided fromCallScopePropertiesin@CallScopeIGuicedWebSocket→ bound toGuicedWebSocketMultibinderextension points forGuicedWebSocketOnAddToGroup,GuicedWebSocketOnRemoveFromGroup, andGuicedWebSocketOnPublish
IGuiceContext.instance()
└─ IGuiceModule hooks
└─ VertxWebSocketsModule (binds ServerWebSocket, IGuicedWebSocket, SPI multibinders)
└─ IGuicePostStartup hooks
└─ VertxSocketHttpWebSocketConfigurator (sortOrder = 55)
├─ HttpServerOptions builder (applies WebSocketServerOptions)
├─ HttpServer builder (registers webSocketHandler with group/scope setup)
└─ Router builder (no-op pass-through)
com.guicedee.vertx.sockets
├── com.guicedee.vertx.web (HTTP server, Router, BodyHandler)
├── com.guicedee.client (CallScope, SPI contracts, IGuicedWebSocket)
├── io.vertx.core (Vertx, ServerWebSocket, EventBus)
└── org.jspecify (nullability annotations)
Module name: com.guicedee.vertx.sockets
The module:
- exports
com.guicedee.vertx.websockets - provides
IGuicePostStartup,VertxHttpServerConfigurator,VertxHttpServerOptionsConfiguratorwithVertxSocketHttpWebSocketConfigurator - provides
IGuiceModulewithVertxWebSocketsModule - uses
IWebSocketMessageReceiver,IOnCallScopeEnter,IOnCallScopeExit,GuicedWebSocketOnAddToGroup,GuicedWebSocketOnRemoveFromGroup,GuicedWebSocketOnPublish
In non-JPMS environments, META-INF/services discovery still works.
| Class | Package | Role |
|---|---|---|
VertxSocketHttpWebSocketConfigurator |
websockets |
Registers the WebSocket handler, configures server options, manages group EventBus consumers |
GuicedWebSocket |
websockets |
@CallScope facade — group management, message broadcast, inbound message dispatch |
WebSocketServerOptions |
websockets |
@Singleton configurable options (compression, frame sizes, timeouts) |
WebSocketException |
websockets |
Unchecked exception for WebSocket operation failures |
VertxWebSocketsModule |
implementations |
Guice module — binds ServerWebSocket, IGuicedWebSocket, SPI multibinders |
IGuicedWebSocket |
client (SPI) |
Contract for group management and message broadcasting |
IWebSocketMessageReceiver |
client (SPI) |
Contract for action-based inbound message handling |
WebSocketMessageReceiver |
client (SPI) |
DTO for deserialized inbound WebSocket messages |
GuicedWebSocketOnAddToGroup |
client (SPI) |
Hook for intercepting group join operations |
GuicedWebSocketOnRemoveFromGroup |
client (SPI) |
Hook for intercepting group leave operations |
GuicedWebSocketOnPublish |
client (SPI) |
Hook for intercepting broadcast operations |
Issues and pull requests are welcome — please add tests for new message receivers, group behaviors, or server configurations.