How to deploy Tyrus (JSR 356 implementation) to a web container?

Learn how to deploy tyrus (jsr 356 implementation) to a web container? with practical examples, diagrams, and best practices. Covers java, tomcat, deployment development techniques with visual expl...

Deploying Tyrus (JSR 356) to Web Containers: A Comprehensive Guide

Hero image for How to deploy Tyrus (JSR 356 implementation) to a web container?

Learn how to successfully deploy Tyrus, the reference implementation for JSR 356 (Java API for WebSocket), to popular web containers like Apache Tomcat.

Tyrus is the reference implementation of the Java API for WebSocket (JSR 356), providing a robust and flexible framework for building WebSocket applications in Java. While Tyrus can be used in standalone applications, deploying it within a web container like Apache Tomcat is a common scenario for enterprise applications. This guide will walk you through the necessary steps and configurations to ensure a smooth deployment of your Tyrus-based WebSocket applications.

Understanding Tyrus Deployment in Web Containers

When deploying a JSR 356 (Tyrus) application to a web container, it's crucial to understand how the container discovers and initializes WebSocket endpoints. JSR 356 leverages the Servlet 3.0+ API's pluggability features, allowing containers to scan for WebSocket annotations (@ServerEndpoint) or programmatic configurations. Tyrus provides its own TyrusServerContainer which integrates with the container's ServletContext to manage WebSocket lifecycle events.

flowchart TD
    A[Web Container Startup] --> B{Scan for JSR 356 Endpoints}
    B --> C{Discover @ServerEndpoint Annotations}
    C --> D[Initialize TyrusServerContainer]
    D --> E[Register WebSocket Endpoints]
    E --> F[WebSocket Application Ready]

Tyrus WebSocket Endpoint Discovery Flow

Prerequisites and Dependencies

Before deploying your Tyrus application, ensure your project includes the necessary dependencies and that your web container meets the minimum requirements. For Tyrus, you'll typically need the tyrus-container-servlet dependency, which bridges Tyrus with the Servlet API. Your web container must support at least Servlet 3.0 and WebSocket 1.0 (JSR 356). Apache Tomcat 7.0.27+ or Jetty 9+ are common choices.

<dependency>
    <groupId>org.glassfish.tyrus.containers</groupId>
    <artifactId>tyrus-container-servlet</artifactId>
    <version>2.1.5</version> <!-- Use the latest stable version -->
</dependency>
<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-server</artifactId>
    <version>2.1.5</version>
</dependency>
<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-client</artifactId>
    <version>2.1.5</version>
</dependency>

Maven dependencies for Tyrus WebSocket application

Configuring Your Web Application for Tyrus

To enable Tyrus within your web application, you typically don't need extensive web.xml configuration if you're using @ServerEndpoint annotations. The tyrus-container-servlet module includes a ServletContainerInitializer that automatically registers the Tyrus WebSocket runtime. However, for programmatic endpoint registration or specific configurations, you might need to interact with the ServerContainer directly.

package com.example.websocket;

import jakarta.websocket.OnClose;
import jakarta.websocket.OnError;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.Session;
import jakarta.websocket.server.ServerEndpoint;

@ServerEndpoint("/websocket/echo")
public class EchoWebSocketEndpoint {

    @OnOpen
    public void onOpen(Session session) {
        System.out.println("WebSocket opened: " + session.getId());
    }

    @OnMessage
    public String onMessage(String message, Session session) {
        System.out.println("Message from " + session.getId() + ": " + message);
        return "Echo: " + message;
    }

    @OnClose
    public void onClose(Session session) {
        System.out.println("WebSocket closed: " + session.getId());
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        System.err.println("WebSocket error on " + session.getId() + ": " + throwable.getMessage());
    }
}

Example of a simple Tyrus @ServerEndpoint

For programmatic registration, you would typically implement jakarta.servlet.ServletContextListener and register endpoints within the contextInitialized method. This approach is useful when endpoints are dynamically created or require complex configuration not easily expressed via annotations.

package com.example.websocket;

import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;
import jakarta.websocket.DeploymentException;
import jakarta.websocket.server.ServerContainer;
import jakarta.websocket.server.ServerEndpointConfig;

@WebListener
public class WebSocketConfigurator implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServerContainer sc = (ServerContainer) sce.getServletContext().getAttribute("jakarta.websocket.server.ServerContainer");
        if (sc != null) {
            try {
                // Programmatic registration of an endpoint
                sc.addEndpoint(ServerEndpointConfig.Builder.create(
                    ProgrammaticEchoEndpoint.class, "/websocket/programmatic-echo").build()
                );
                System.out.println("Programmatic WebSocket endpoint registered.");
            } catch (DeploymentException e) {
                System.err.println("Error deploying programmatic WebSocket endpoint: " + e.getMessage());
            }
        } else {
            System.err.println("ServerContainer not found. WebSocket support might be missing.");
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // Clean up resources if necessary
    }
}

Programmatic WebSocket endpoint registration using ServletContextListener

Deployment Steps for Apache Tomcat

Deploying your Tyrus application to Apache Tomcat involves packaging your application as a WAR file and placing it in Tomcat's webapps directory. Tomcat, being a Servlet 3.0+ compliant container, will automatically detect and initialize your WebSocket endpoints.

1. Package Your Application

Build your Java project into a Web Application Archive (WAR) file. If you're using Maven, the maven-war-plugin typically handles this. Ensure all Tyrus dependencies are correctly packaged within the WAR (usually in WEB-INF/lib).

2. Deploy to Tomcat

Copy the generated .war file into the webapps directory of your Tomcat installation. Tomcat will automatically unpack and deploy the application. For example, if your WAR is named mywebsocketapp.war, it will be deployed to http://localhost:8080/mywebsocketapp.

3. Verify Deployment

Check Tomcat's logs (catalina.out or localhost.<date>.log) for messages indicating successful deployment and WebSocket endpoint registration. You should see messages from Tyrus confirming the endpoints are active. Then, try connecting to your WebSocket endpoint using a client (e.g., a simple HTML page with JavaScript or a WebSocket testing tool).

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Test</title>
</head>
<body>
    <h1>Tyrus WebSocket Echo Test</h1>
    <input type="text" id="messageInput" placeholder="Type a message">
    <button onclick="sendMessage()">Send</button>
    <div id="output"></div>

    <script>
        const output = document.getElementById('output');
        const messageInput = document.getElementById('messageInput');
        const ws = new WebSocket('ws://localhost:8080/mywebsocketapp/websocket/echo'); // Adjust URL as needed

        ws.onopen = function() {
            output.innerHTML += '<p>Connected to WebSocket!</p>';
        };

        ws.onmessage = function(event) {
            output.innerHTML += '<p>Received: ' + event.data + '</p>';
        };

        ws.onclose = function() {
            output.innerHTML += '<p>Disconnected from WebSocket.</p>';
        };

        ws.onerror = function(error) {
            output.innerHTML += '<p style="color:red;">Error: ' + error.message + '</p>';
        };

        function sendMessage() {
            const message = messageInput.value;
            if (message) {
                ws.send(message);
                output.innerHTML += '<p>Sent: ' + message + '</p>';
                messageInput.value = '';
            }
        }
    </script>
</body>
</html>

Simple HTML/JavaScript client to test the Echo WebSocket endpoint