6.24.1 Using @ServerWebSocket

The @ServerWebSocket annotation can be applied to any class that should map to a WebSocket URI. The following example is a simple chat WebSocket implementation:

WebSocket Chat Example

  1. import io.micronaut.websocket.WebSocketBroadcaster;
  2. import io.micronaut.websocket.WebSocketSession;
  3. import io.micronaut.websocket.annotation.OnClose;
  4. import io.micronaut.websocket.annotation.OnMessage;
  5. import io.micronaut.websocket.annotation.OnOpen;
  6. import io.micronaut.websocket.annotation.ServerWebSocket;
  7. import java.util.function.Predicate;
  8. @ServerWebSocket("/chat/{topic}/{username}") (1)
  9. public class ChatServerWebSocket {
  10. private WebSocketBroadcaster broadcaster;
  11. public ChatServerWebSocket(WebSocketBroadcaster broadcaster) {
  12. this.broadcaster = broadcaster;
  13. }
  14. @OnOpen (2)
  15. public void onOpen(String topic, String username, WebSocketSession session) {
  16. String msg = "[" + username + "] Joined!";
  17. broadcaster.broadcastSync(msg, isValid(topic, session));
  18. }
  19. @OnMessage (3)
  20. public void onMessage(
  21. String topic,
  22. String username,
  23. String message,
  24. WebSocketSession session) {
  25. String msg = "[" + username + "] " + message;
  26. broadcaster.broadcastSync(msg, isValid(topic, session)); (4)
  27. }
  28. @OnClose (5)
  29. public void onClose(
  30. String topic,
  31. String username,
  32. WebSocketSession session) {
  33. String msg = "[" + username + "] Disconnected!";
  34. broadcaster.broadcastSync(msg, isValid(topic, session));
  35. }
  36. private Predicate<WebSocketSession> isValid(String topic, WebSocketSession session) {
  37. return s -> s != session && topic.equalsIgnoreCase(s.getUriVariables().get("topic", String.class, null));
  38. }
  39. }

WebSocket Chat Example

  1. import io.micronaut.websocket.WebSocketBroadcaster
  2. import io.micronaut.websocket.WebSocketSession
  3. import io.micronaut.websocket.annotation.OnClose
  4. import io.micronaut.websocket.annotation.OnMessage
  5. import io.micronaut.websocket.annotation.OnOpen
  6. import io.micronaut.websocket.annotation.ServerWebSocket
  7. import java.util.function.Predicate
  8. @ServerWebSocket("/chat/{topic}/{username}") (1)
  9. class ChatServerWebSocket {
  10. private WebSocketBroadcaster broadcaster
  11. ChatServerWebSocket(WebSocketBroadcaster broadcaster) {
  12. this.broadcaster = broadcaster
  13. }
  14. @OnOpen (2)
  15. void onOpen(String topic, String username, WebSocketSession session) {
  16. String msg = "[" + username + "] Joined!"
  17. broadcaster.broadcastSync(msg, isValid(topic, session))
  18. }
  19. @OnMessage (3)
  20. void onMessage(
  21. String topic,
  22. String username,
  23. String message,
  24. WebSocketSession session) {
  25. String msg = "[" + username + "] " + message
  26. broadcaster.broadcastSync(msg, isValid(topic, session)) (4)
  27. }
  28. @OnClose (5)
  29. void onClose(
  30. String topic,
  31. String username,
  32. WebSocketSession session) {
  33. String msg = "[" + username + "] Disconnected!"
  34. broadcaster.broadcastSync(msg, isValid(topic, session))
  35. }
  36. private Predicate<WebSocketSession> isValid(String topic, WebSocketSession session) {
  37. return { s -> s != session && topic.equalsIgnoreCase(s.getUriVariables().get("topic", String.class, null)) }
  38. }
  39. }

WebSocket Chat Example

  1. import io.micronaut.websocket.WebSocketBroadcaster
  2. import io.micronaut.websocket.WebSocketSession
  3. import io.micronaut.websocket.annotation.OnClose
  4. import io.micronaut.websocket.annotation.OnMessage
  5. import io.micronaut.websocket.annotation.OnOpen
  6. import io.micronaut.websocket.annotation.ServerWebSocket
  7. import java.util.function.Predicate
  8. @ServerWebSocket("/chat/{topic}/{username}") (1)
  9. class ChatServerWebSocket(private val broadcaster: WebSocketBroadcaster) {
  10. @OnOpen (2)
  11. fun onOpen(topic: String, username: String, session: WebSocketSession) {
  12. val msg = "[$username] Joined!"
  13. broadcaster.broadcastSync(msg, isValid(topic, session))
  14. }
  15. @OnMessage (3)
  16. fun onMessage(
  17. topic: String,
  18. username: String,
  19. message: String,
  20. session: WebSocketSession) {
  21. val msg = "[$username] $message"
  22. broadcaster.broadcastSync(msg, isValid(topic, session)) (4)
  23. }
  24. @OnClose (5)
  25. fun onClose(
  26. topic: String,
  27. username: String,
  28. session: WebSocketSession) {
  29. val msg = "[$username] Disconnected!"
  30. broadcaster.broadcastSync(msg, isValid(topic, session))
  31. }
  32. private fun isValid(topic: String, session: WebSocketSession): Predicate<WebSocketSession> {
  33. return Predicate<WebSocketSession>{ s -> (s !== session && topic.equals(s.getUriVariables().get("topic", String::class.java, null), ignoreCase = true)) }
  34. }
  35. }
1The @ServerWebSocket annotation is used to define the path the WebSocket is mapped under. The URI can be a URI template.
2The @OnOpen annotation is used to declare a method that is invoked when the WebSocket is opened.
3The @OnMessage annotation is used to declare a method that is invoked when a message is received.
4You can use a WebSocketBroadcaster to broadcast messages to every WebSocket session. You can filter which sessions to communicate with a Predicate. Also, you could use the passed WebSocketSession instance to send a message to it with WebSocketSession::send.
5The @OnClose annotation is used to declare a method that is invoked when the WebSocket is closed.
A working example of WebSockets in action can be found in the Micronaut Examples GitHub repository.

In terms of binding the method arguments to each WebSocket method can be:

  • A variable from the URI template (in the above example topic and username are variables in the URI template)

  • An instance of WebSocketSession

The @OnClose Method

The @OnClose method can also optionally receive a CloseReason. The @OnClose method is invoked prior to the session closing.

The @OnMessage Method

The @OnMessage method can define a parameter that is the message body. The parameter can be one of the following:

  • A Netty WebSocketFrame

  • Any Java primitive or simple type (such as String). In fact any type that can be converted from ByteBuf (you can register additional TypeConverter beans if you wish to support a custom type).

  • A byte[], a ByteBuf or a Java NIO ByteBuffer.

  • A Plain Old Java Object (POJO). In the case of a POJO the POJO will be decoded by default as JSON using JsonMediaTypeCodec. You can register a custom codec if necessary and define the content type of the handler using the @Consumes annotation.

The @OnError Method

A method annotated with @OnError can be added to implement custom error handling. The @OnError method can optionally define a parameter that receives the exception type that is to be handled. If no @OnError handling is present and a unrecoverable exception occurs the WebSocket is automatically closed.

Non-Blocking Message Handling

The previous example uses the broadcastSync method of the WebSocketBroadcaster interface which blocks until the broadcast is complete. A similar sendSync method exists in WebSocketSession to send a message to a single receiver in a blocking manner. You can however implement non-blocking WebSocket servers by instead returning a Publisher or a Future from each WebSocket handler method. For example:

WebSocket Chat Example

  1. @OnMessage
  2. public Publisher<Message> onMessage(
  3. String topic,
  4. String username,
  5. Message message,
  6. WebSocketSession session) {
  7. String text = "[" + username + "] " + message.getText();
  8. Message newMessage = new Message(text);
  9. return broadcaster.broadcast(newMessage, isValid(topic, session));
  10. }

WebSocket Chat Example

  1. @OnMessage
  2. Publisher<Message> onMessage(
  3. String topic,
  4. String username,
  5. Message message,
  6. WebSocketSession session) {
  7. String text = "[" + username + "] " + message.getText()
  8. Message newMessage = new Message(text)
  9. broadcaster.broadcast(newMessage, isValid(topic, session))
  10. }

WebSocket Chat Example

  1. @OnMessage
  2. fun onMessage(
  3. topic: String,
  4. username: String,
  5. message: Message,
  6. session: WebSocketSession): Publisher<Message> {
  7. val text = "[" + username + "] " + message.text
  8. val newMessage = Message(text)
  9. return broadcaster.broadcast(newMessage, isValid(topic, session))
  10. }

The example above uses broadcast, which creates an instance of Publisher and returns the value to Micronaut. Micronaut will then take care of sending the message asynchronously based on the Publisher interface. The similar send method can be used to send a single message asynchronously via Micronaut return value.

For sending messages asynchronously outside Micronaut annotated handler methods, you can use broadcastAsync and sendAsync methods in their respective WebSocketBroadcaster and WebSocketSession interfaces. For blocking sends, the broadcastSync and sendSync methods can be used.

@ServerWebSocket and Scopes

By default a unique @ServerWebSocket instance is created for each WebSocket connection. This allows you to retrieve the WebSocketSession from the @OnOpen handler and assign it to a field of the @ServerWebSocket instance.

If you define the @ServerWebSocket as @Singleton it should be noted that extra care will need to be taken to synchronize local state to avoid thread safety issues.

Sharing Sessions with the HTTP Session

The WebSocketSession is by default backed by an in-memory map. If you add the the session module you can however share sessions between the HTTP server and the WebSocket server.

When sessions are backed by a persistent store such as Redis then after each message is processed the session is updated to the backing store.
Using the CLI

If you have created your project using the Micronaut CLI and the default (service) profile, you can use the create-websocket-server command to create a class with WebSocketServer.

  1. $ mn create-websocket-server MyChat
  2. | Rendered template WebsocketServer.java to destination src/main/java/example/MyChatServer.java

Connection Timeouts

By default Micronaut will timeout idle connections that have no activity after 5 minutes. Normally this is not a problem as browsers will automatically reconnect WebSocket sessions, however you can control this behaviour by setting the micronaut.server.idle-timeout setting (a negative value will result no timeout):

Setting the Connection Timeout for the Server

  1. micronaut:
  2. server:
  3. idle-timeout: 30m # 30 minutes

If you are using Micronaut’s WebSocket client then you may also wish to set the timeout on the client:

Setting the Connection Timeout for the Client

  1. micronaut:
  2. http:
  3. client:
  4. read-idle-timeout: 30m # 30 minutes