Chapter 1. Getting Started

This chapter provides a quick introduction on how to get started building WebSocket services using Java API for WebSocket and Tyrus. The example described here presents how to implement simple websocket service as JavaEE web application that can be deployed on any servlet container supporting Servlet 5 and higher. It also discusses starting Tyrus in standalone mode.

1.1. WebSocket Services Using Java API for WebSocket

First, to use the Java API for WebSocket in your project you need to depend on the following artifact:

<dependency>
    <groupId>jakarta.websocket</groupId>
    <artifactId>jakarta.websocket-api</artifactId>
    <version>2.1.1</version>
</dependency>

1.1.1. Creating Annotated Server Endpoint

In this section we will create a simple server side websocket endpoint which will echo the received message back to the sender. We will deploy this endpoint on the container.

In Java API for WebSocket and Tyrus, there are two basic approaches how to create an endpoint - either annotated endpoint, or programmatic endpoint. By annotated endpoint we mean endpoint constructed by using annotations (jakarta.websocket.server.ServerEndpoint for server endpoint and jakarta.websocket.ClientEndpoint for client endpoint), like in "Annotated Echo Endpoint".

Example 1.1. Annotated Echo Endpoint

@ServerEndpoint(value = "/echo")
public class EchoEndpointAnnotated {
    @OnMessage
    public String onMessage(String message, Session session) {
        return message;
    }
}


The functionality of the EchoEndpointAnnotated is fairly simple - to send the received message back to the sender. To turn a POJO (Plain Old Java Object) to WebSocket server endpoint, the annotation @ServerEndpoint(value = "/echo") needs to be put on the POJO - see line 1. The URI path of the endpoint is "/echo". The annotation @OnMessage - line 3 on the method public String onMessage(String message, Session session) indicates that this method will be called whenever text message is received. On line 5 in this method the message is sent back to the user by returning it from the message.

The application containing only the EchoEndpointAnnotated class can be deployed to the container.

1.1.2. Client Endpoint

Let's create the client part of the application. The client part may be written in JavaScript or any other technology supporting WebSockets. We will use Java API for WebSocket and Tyrus to demonstrate how to develop programmatic client endpoint. The following code is used as a client part to communicate with the EchoEndpoint deployed on server using Tyrus and Java API for WebSocket.

The example "Client Endpoint" utilizes the concept of the programmatic endpoint. By programmatic endpoint we mean endpoint which is created by extending class jakarta.websocket.Endpoint. The example is standalone java application which needs to depend on some Tyrus artifacts to work correctly, see "Tyrus Standalone Mode". In the example first the CountDownLatch is initialized. It is needed as a bocking data structure - on line 31 it either waits for 100 seconds, or until it gets counted down (line 22). On line 9 the jakarta.websocket.ClientEndpointConfig is created - we will need it later to connect the endpoint to the server. On line 11 the org.glassfish.tyrus.client.ClientManager is created. it implements the jakarta.websocket.WebSocketContainer and is used to connect to server. This happens on next line. The client endpoint functionality is contained in the jakarta.websocket.Endpoint lazy instantiation. In the onOpen method new MessageHandler is registered (the received message is just printed on the console and the latch is counted down). After the registration the message is sent to the server (line 25).

Example 1.2. Client Endpoint

public class DocClient {
    private static CountDownLatch messageLatch;
    private static final String SENT_MESSAGE = "Hello World";

    public static void main(String [] args){
        try {
            messageLatch = new CountDownLatch(1);

            final ClientEndpointConfig cec = ClientEndpointConfig.Builder.create().build();

            ClientManager client = ClientManager.createClient();
            client.connectToServer(new Endpoint() {

                @Override
                public void onOpen(Session session, EndpointConfig config) {
                    try {
                        session.addMessageHandler(new MessageHandler.Whole<String>() {

                            @Override
                            public void onMessage(String message) {
                                System.out.println("Received message: "+message);
                                messageLatch.countDown();
                            }
                        });
                        session.getBasicRemote().sendText(SENT_MESSAGE);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }, cec, new URI("ws://localhost:8025/websockets/echo"));
            messageLatch.await(100, TimeUnit.SECONDS);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


1.1.3. Creating Server Endpoint Programmatically

Similarly to "Client Endpoint" the server registered endpoint may also be the programmatic one:

Example 1.3. Programmatic Echo Endpoint

public class EchoEndpointProgrammatic extends Endpoint {
    @Override
    public void onOpen(final Session session, EndpointConfig config) {
        session.addMessageHandler(new MessageHandler.Whole<String>() {
            @Override
            public void onMessage(String message) {
                try {
                    session.getBasicRemote().sendText(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}


The functionality of the EchoEndpointProgrammatic is fairly simple - to send the received message back to the sender. The programmatic server endpoint needs to extend jakarta.websocket.Endpoint - line 1. Mehod public void onOpen(final Session session, EndpointConfig config) gets called once new connection to this endpoin0t is opened. In this method the MessageHandler is registered to the jakarta.websocket.Session instance, which opened the connection. Method public void onMessage(String message) gets called once the message is received. On line 8 the message is sent back to the sender.

To see how both annotated and programmatic endpoints may be deployed please check the section Deployment. In short: you need to put the server endpoint classes into WAR, deploy on server and the endpoints will be scanned by server and deployed.

1.1.4. Tyrus in Standalone Mode

To use Tyrus in standalone mode it is necessary to depend on correct Tyrus artifacts. The following artifacts need to be added to your pom to use Tyrus:

<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-server</artifactId>
    <version>2.1.5</version>
</dependency>

<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-container-grizzly-server</artifactId>
    <version>2.1.5</version>
</dependency>

Let's use the very same example like for Java API for WebSocket and deploy the EchoEndpointAnnotated on the standalone Tyrus server on the hostname "localhost", port 8025 and path "/websockets", so the endpoint will be available at address "ws://localhost:8025/websockets/echo".

public void runServer() {
    Server server = new Server("localhost", 8025, "/websockets", null, EchoEndpoint.class);

    try {
        server.start();
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        System.out.print("Please press a key to stop the server.");
        reader.readLine();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        server.stop();
    }
}

1.2. Creating a New Project from Maven Archetype

1.2.1. Executing maven

Since starting from a Maven project is the most convenient way for working with Tyrus, let's now have a look at this approach. We will now create a new Tyrus project that runs on top of a Grizzly container. We will use a Tyrus-provided maven archetype. To create the project, execute the following Maven command in the directory where the new project should reside:
mvn archetype:generate -DarchetypeArtifactId=tyrus-archetype-echo \
-DarchetypeGroupId=org.glassfish.tyrus.archetypes -DinteractiveMode=false \
-DgroupId=com.example -DartifactId=echo-service -Dpackage=com.example \
-DarchetypeVersion=2.1.5
Feel free to adjust the groupId, package and/or artifactId of your new project. Alternatively, you can change it by updating the new project pom.xml once it gets generated.

1.2.2. Exploring the Newly Created Project

Once the project generation from a Tyrus maven archetype is successfully finished, you should see the new echo-service project directory created in your current location. The directory contains a standard Maven project structure:

Project build and management configuration is described in the pom.xml located in the project root directory.
Project sources are located under src/main/java.
Project resources are located under src/main/resources.
Project web application files are located under src/main/webapp.

The project contains the simple EchoEndpoint class which looks (similar to "Annotated Echo Endpoint") as follows:

package com.example;

import jakarta.websocket.OnMessage;
import jakarta.websocket.Session;
import jakarta.websocket.server.ServerEndpoint;

/**
* Server endpoint "listening" on path "/echo".
*/
@ServerEndpoint("/echo")
public class EchoEndpoint {

    /**
    * Method handling incoming text messages.
    * <p></p>
    * This implementations just sends back received message.
    *
    * @param message received message.
    * @return returned value will be sent back to client. You can also declare this method to not return anything. In
    * that case, you would need to obtain {@link jakarta.websocket.Session} object and call
    * {@link jakarta.websocket.Session#getBasicRemote()#sendText();} in order to send message.
    */
    @OnMessage
    public String echo(String message) {
        return message;
    }
}

Another piece of code that has been generated in this skeleton project is a EchoTest unit test class that is located in the same com.example package as the EchoEndpoint class, however, this unit test class is placed into the maven project test source directory src/test/java (similar to "Client Endpoint"):

public class EchoTest extends TestContainer {

    public static final String MESSAGE = "Do or do not, there is no try.";

    @Test
    public void testEcho() throws DeploymentException, InterruptedException, IOException {
        final Server server = startServer(EchoEndpoint.class);

        final CountDownLatch messageLatch = new CountDownLatch(1);

        try {
            final ClientManager client = ClientManager.createClient();
            client.connectToServer(new Endpoint() {
                @Override
                public void onOpen(Session session, EndpointConfig EndpointConfig) {

                    try {
                        session.addMessageHandler(new MessageHandler.Whole<String>() {
                            @Override
                            public void onMessage(String message) {
                                System.out.println("# Received: " + message);

                                if (message.equals(MESSAGE)) {
                                    messageLatch.countDown();
                                }
                            }
                        });

                        session.getBasicRemote().sendText(MESSAGE);
                    } catch (IOException e) {
                        // do nothing
                    }
                }
            }, ClientEndpointConfig.Builder.create().build(), getURI(EchoEndpoint.class));

            assertTrue(messageLatch.await(1, TimeUnit.SECONDS));

        } catch (Exception e) {
            e.printStackTrace();
            fail();
        } finally {
            stopServer(server);
        }
    }
}

In this unit test, a Grizzly container is first started and server application is deployed using the static call TestContainer.startServer(). Next, WebSocket client instance client is built and then a programmatic WebSocket Endpoint is used to connect to the EchoEndpoint deployed on the Grizzly server at ws://localhost:8025/e2e-test/echo. The value of port 8025 and context path e2e-test are default values preset at the TestContainer.

The generated project structure also contains an index.html file, stored in src/test/webapp folder. This web page contains a javascript WebSocket client that is capable of sending WebSocket requests to the EchoEndpoint. This web page is deployed along the war application in the WebSocket container such as Tyrus.

1.2.3. Building the generated project

Now that we have seen the content of the project, let's try to test-run it. To do this, we need to invoke following command on the command line:

mvn clean package

This will compile the project and run the project unit tests. We should see a similar output that informs about a successful build once the build is finished:

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] Packaging webapp
[INFO] Assembling webapp [echo-service] in [.../echo-service/target/echo-service]
[INFO] Processing war project
[INFO] Copying webapp resources [.../echo-service/src/main/webapp]
[INFO] Building war: .../echo-service/target/echo-service.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.979 s
[INFO] Finished at: 2022-01-28T14:23:45+01:00
[INFO] Final Memory: 17M/490M
[INFO] ------------------------------------------------------------------------

Now you are ready to take the packaged WAR (located under ./target/echo-service.war) and deploy it to a WebSocket container of your choice.

Important

To deploy the application, you will need a WebSocket compliant container (such as Tyrus). Tyrus can be found for instance in Glassfish 7. It is also possible to create a packaged WAR with Tyrus jars included to deploy to a Servlet 5/Servlet 6 compliant container.

1.2.4. Running the deployed application

Once the war is deployed, it can be run on the deployment URI such as localhost:8080/echo-service/(the included index.html web page can be opened in a web browser). The web page has a title Web Socket Echo Client and contains a button with a label Press me and a text field prefilled with Hello Web Sockets ! When the button is pressed, the WebSocket echo service queried and the web page contains a communication:

CONNECTED

SENT: Hello Web Sockets !

RECEIVED: Hello Web Sockets !