WebSocket Introduction

Helidon integrates with Tyrus to provide support for the Jakarta WebSocket API. The WebSocket API enables Java applications to participate in WebSocket interactions as both servers and clients. The server API supports two flavors: annotated and programmatic endpoints.

Annotated endpoints, as suggested by their name, use Java annotations to provide the necessary meta-data to define WebSocket handlers; programmatic endpoints implement API interfaces and are annotation free. Annotated endpoints tend to be more flexible since they allow different method signatures depending on the application needs, whereas programmatic endpoints must implement an interface and are, therefore, bounded to its definition.

Helidon MP support is centered around annotations and bean discovery using CDI. Developers can choose between annotated and programmatic endpoints or use any combination of them. Using annotated endpoints is recommended in MP as they usually result in more succinct and easier-to-read code.

Helidon MP Example

This section describes the implementation of a simple application that uses a REST resource to push messages into a shared queue and a WebSocket endpoint to download messages from the queue, one at a time, over a connection. The example will show how REST and WebSocket connections can be seamlessly combined into a Helidon application.

The Helidon MP application shown here takes full advantage of CDI and class scanning and does not require any additional code given that the necessary information is available from the code annotations.

The REST endpoint is implemented as a JAX-RS resource, and the shared queue (in application scope) is directly injected:

@Path("rest")
public class MessageQueueResource {

    @Inject
    private MessageQueue messageQueue;

    @POST
    @Consumes("text/plain")
    public void push(String s) {
        messageQueue.push(s);
    }
}
Copied

Here we opt for the use of an annotated WebSocket endpoint decorated by @ServerEndpoint that provides all the meta-data necessary for Helidon to create the endpoint.

@ServerEndpoint(
        value = "/websocket",
        encoders = { UppercaseEncoder.class })
public class MessageBoardEndpoint {

    @Inject
    private MessageQueue messageQueue;

    @OnMessage
    public void onMessage(Session session, String message) {
        if (message.equals("SEND")) {
            while (!messageQueue.isEmpty()) {
                session.getBasicRemote().sendObject(messageQueue.pop());
            }
        }
    }
}
Copied

Since MessageBoardEndpoint is just a POJO, it uses additional annotations for event handlers such as @OnMessage. One advantage of this approach, much like in the JAX-RS API, is that method signatures are not fixed. In the snipped above, the parameters (which could be specified in any order!) include the WebSocket session and the message received that triggered the call.

So what else is needed to run this Helidon MP app? Nothing else other than the supporting classes MessageQueue and UppercaseEncoder. Helidon MP declares both @Path and @ServerEndpoint as bean defining annotation, so all that is needed is for CDI discovery to be enabled --typically in your beans.xml file.

By default, both JAX-RS resources and WebSocket endpoints will be available under the root path "/". This default value can be overridden by providing subclasses/implementations for jakarta.ws.rs.Application and jakarta.websocket.server.ServerApplicationConfig, respectively. JAX-RS uses @ApplicationPath on application subclasses to provide this root path, but since there is no equivalent in the WebSocket API, Helidon MP uses its own annotation @RoutingPath on jakarta.websocket.server.ServerApplicationConfig implementations.

For instance, if in our example we include the following class:

@ApplicationScoped
@RoutingPath("/web")
public class MessageBoardApplication implements ServerApplicationConfig {
    @Override
    public Set<ServerEndpointConfig> getEndpointConfigs(
            Set<Class<? extends Endpoint>> endpoints) {
        assert endpoints.isEmpty();
        return Collections.emptySet();      // No programmatic endpoints
    }

    @Override
    public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> endpoints) {
        return endpoints;       // Returned scanned endpoints
    }
}
Copied

the root path for WebSocket endpoints will be "/web" instead of the default "/". Note that @RoutingPath is not a bean defining annotation, thus the need to use @ApplicationScoped --which, as before, requires CDI bean discovery mode to be annotated. In addition to @RoutingPath, these classes can be annotated with @RoutingName to associate an endpoint with a Helidon named socket. Please refer to the Javadoc of that annotation for additional information.

All endpoint methods in Helidon MP are executed in a separate thread pool, independently of Netty. Therefore, there is no need to create additional threads for blocking or long-running operations as these will not affect Netty’s ability to process networking data.

For more information see the example.