This is a short guide on how to use an embedded Undertow webserver coupled with Guice for IoC, to expose a WebSocket endpoint. You can check out a sample project’s source code on github.

A custom ClassIntrospector is used to construct a ServerEndpoint instance that expects a dependency to be injected through it’s @Inject annotated constructor.

A Basic WebSocket Deployment

Exposing a WebSocket endpoint with JSR 356 became as easy as:

@ServerEndpoint("/")
class WebSocketEndpoint {
  @OnOpen
  public void connect(Session session) throws IOException { /*...*/ }

  @OnMessage
  public void message(String message, Session session) { /*...*/ }

  @OnClose
  public void close(CloseReason closeReason, Session session) { /*...*/ }
}

The following class exposes the endpoint using an embedded Undertow server.

public class WebSocketService {
  public static void main(String [] args) {
    final WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo()
        .addEndpoint(WebSocketEndpoint.class);

    DeploymentInfo websocketDeployment = deployment()
        .setContextPath("/ws")
        .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, webSocketDeploymentInfo)
        .setDeploymentName("websocket-deployment")
        .setClassLoader(Main.class.getClassLoader());

    DeploymentManager manager = Servlets.defaultContainer()
        .addDeployment(websocketDeployment);

    manager.deploy();

    try {
      Undertow server = Undertow.builder()
          .addHttpListener(8080, "localhost")
          .setHandler(path().addPrefixPath("/ws", manager.start()))
          .build();

      server.start();
    } catch (ServletException e) {
      e.printStackTrace();
    }
  }
}

Injecting Dependencies

The WebSocketEndpoint class requires a SessionHolder instance that keeps track of all connected users. The SessionHolder’s construction and injection should be handled by Guice.

@ServerEndpoint("/")
class WebSocketEndpoint {

  private final SessionHolder sessionHolder;

  @Inject
  public WebSocketEndpoint(SessionHolder sessionHolder) {
    this.sessionHolder = sessionHolder;
  }
  /* ... */
}

Since a class marked with the ServerEndpoint annotation is expected to have a 0-ary constructor, the WebSocket deployment will fail at runtime throwing a javax.websocket.DeploymentException. A custom ClassIntrospector can be used to provide an Instance Factory which is aware of Guice and can properly handle object construction.

// This is a nested class inside the WebSocketService
// It has access to the `injector` field of it's parent class.
private class GuiceClassIntrospector implements ClassIntrospecter {
    @Override
    public <T> InstanceFactory<T> createInstanceFactory(Class<T> clazz) throws NoSuchMethodException {
      return new ImmediateInstanceFactory<>(injector.getInstance(clazz));
    }
  }

The GuiceClassIntrospector has to be registered with a DeploymentInfo instance before deployment.

  websocketDeployment = websocketDeployment.setClassIntrospecter(
    new GuiceClassIntrospector());

After registration of the custom ClassIntrospector, all registered endpoint classes will be instantiated through a Guice Injector.