Receiving Messages

From an API perspective, sending WebSocket messages is much simpler because of it's dependency on a simple construct i.e. javax.websocket.RemoteEndpoint interface. For an understanding of how to receive messages, we need to take into account both annotated as well as programmatic styles

  • Annotated endpoints: it's all about passing the right type of parameters in the method annotated with @OnMessage

  • Programmatic endpoints: implementation of the appropriate javax.websocket.MessageHandler (child) interface encapsulates the logic for receiving messages

Receive modes: a quick primer

Here is an overview of the options available when receiving messages

Mode

Description

Complete

The message is received in its entirety

Partial

This works in conjunction with the partial send capability. If the sender sends messages in chunks, the message is received in chunks. The receiver will get a true boolean flag to notify it of the last message part

Streaming

Receive messages in form of Java Readers and InputStreams

Receving messages can also end up with a lot of permutations and combinations (just like the send APIs). So here is what we'll do in order to help tackle that

  • Pick up a message type (thankfully there are just two of them - String and Binary!)

  • For each of the endpoint styles (Annotated and Programmatic), we will look at the possible receiving modes (as mentioned above)

  • Take a look at how Java objects and Pong messages are handled (separate sections)

Receiving text messages

Annotated endpoints

Receving text messages in annotated endpoints is all about having the appropriate method parameter type and the WebSocket runtime will automatically figure out the rest

Complete

...
@OnMessage
public void handleChatMsg(String chat) {
System.out.println("Got message - " + chat);
}
...

Partial

...
@OnMessage
public void pushChunk(String partMsg, boolean last) {
String chunkSeq = last ? "intermediate" : "last" ;
System.out.println("Got " + chunkSeq + " chunk - "+ partMsg);
}
...

Streaming

...
@OnMessage
public void handleChatMsg(Reader charStream) {
System.out.println("reading char stream");
}
...

Programmatic endpoints

As mentioned earlier, Programmatic endpoints are inheritance based and thus need some more custom code to set them up as message receviers.

Complete

public class WholeTextMsgHandler extends MessageHandler.Whole<String> {
@Override
public void onMessage(String chat) {
System.out.println("Got message - " + chat);
}
}

Partial

public class PartialTextMsgHandler extends MessageHandler.Partial<String> {
@Override
public void onMessage(String partMsg, boolean last) {
String chunkSeq = last ? "intermediate" : "last" ;
System.out.println("Got " + chunkSeq + " chunk - "+ partMsg);
}
}

Streaming

public class WholeStreamingTextMsgHandler extends MessageHandler.Whole<Reader> {
@Override
public void onMessage(Reader charStream) {
System.out.println("Got stream message - " + charStream);
}
}

Receiving binary messages

When it comes to binary messages, the pattern (for both annotated and programmatic endpoints) is the same (except for the data type of course!). Binary messages support is available in the form of byte[] (array), java.nio.ByteBuffer and java.io.InputStream

Annotated endpoints

Complete

...
@OnMessage
public void handleImage(ByteBuffer img) {
System.out.println("Got message - " + chat);
}
...

Partial

...
@OnMessage
public void pushChunk(byte[] audioPart, boolean last) {
String chunkSeq = last ? "intermediate" : "last" ;
System.out.println("Got " + chunkSeq + " clip");
}
...

Streaming

...
@OnMessage
public void handleChatMsg(InputStream binaryStream) {
System.out.println("reading binary stream");
}
...

Programmatic endpoints

The concept remains the same apart from a change in the data types..

Complete

public class WholeBinaryMsgHandler extends MessageHandler.Whole<byte[]> {
@Override
public void onMessage(byte[] image) {
System.out.println("Got image - " + image.length);
}
}

Partial

public class PartialBinaryMsgHandler extends MessageHandler.Partial<ByteBuffer> {
@Override
public void onMessage(ByteBuffer clip, boolean last) {
String chunkSeq = last ? "intermediate" : "last" ;
System.out.println("Got " + chunkSeq + " chunk");
}
}

Streaming

public class WholeStreamingBinaryMsgHandler extends MessageHandler.Whole<InputStream> {
@Override
public void onMessage(InputStream binaryStream) {
System.out.println("Got stream binary message");
}
}

Receiving text, binary messages as Java objects

Text and binary messages sent by a WebSocket peer can be received as Java objects within your message handling logic (annotated or programmatic).

But how would the on-wire format (text/binary) to Java object transformation take place ?

This is where Decoders come into the picture. An implementation of a javax.websocket.Decoder provides the necessary logic to convert a message from it's on-wire format into it's Java representation.

WebSocket Decoders: the details

Decoders are complementary to Encoders (which were discussed in the Sending Messages lesson). Take a look at the diagram below to visualize how they work at runtime

Decoders in action

Transforming native data types (Text, Binary) into Java objects

A summary of Decoders and their types

Basic Decoder Type

Description

Decoder

The top level interface for different types of Decoders

Decoder.Text<T>

Defines how a custom Java object (of type T) is produced from a text payload (java.lang.String)

Decoder.Binary<T>

Defines how a custom Java object (of type T) is produced from a binary payload (java.nio.ByteBuffer)

A sample to demonstrate how a Decoder which creates a Subscription object from a String

public class StockSubscriptionDecoder implements Decoder.Text<Subscription> {
@Override
public Subscription decode(String subscription){
//client sends comma seperated list of subscription e.g. appl,goog,orcl
return new Subscription(Arrarys.asList(subscription.split(",")));
}
@Override
public void willDecode(String subscription){
return subscription!=null && subscription.split(",").length > 0;
}
}

Transforming Streaming inputs into Java objects

Native text and binary messages can be received as streams - as explained in the previous sections. Java objects can also be received in a streaming style

Streaming Decoder Type

Description

Decoder.TextStream<T>

Defines how a custom Java object (of type T) is produced from a character stream (java.io.Reader)

Decoder.BinaryStream<T>

Defines how a custom Java object (of type T) is produced from a binary stream (java.io.InputStream)

This example shows how you can handle data in a streaming form using a Reader which creates a Conversation object

public class ConversationDecoder implements Decoder.TextStream<Conversation> {
@Override
//handles new-line delimited content
public Conversation decode(Reader content) {
Conversation conversation = new Conversation();
try(LineNumberReader lineByLineReader = new LineNumberReader(content)){
String line = lineByLineReader.readLine();
while(line != null) {
conversation.add(line);
line = lineByLineReader.readLine();
}
}
return conversation;
}
}

Handling Pong objects

Pong messages were introduced in the API Overview chapter and were then discussed in the Sending Messages chapter as well. Receiving a health-check response message (a.k.a javax.websocket.Pong) is possible in both annotated and programmatic endpoints. Here are the examples

//annotated Pong handler
...
@OnMessage
public void healthCheckCallback(PongMessage pong) {
System.out.println("Pong for Ping! "+ new String(pong.getApplicationData().array());
}
...
//programmatic Pong handler
public class PongMsgHandler extends MessageHandler.Whole<PongMessage> {
@Override
public void onMessage(PongMessage pong) {
System.out.println("Pong for Ping! "+ new String(pong.getApplicationData().array());
}
}

Common notes

Using a MessageHandler

There are two basic steps involved (common to Programmatic endpoints)

  • implement appropriate MessageHandler implementation based on data type and whole/partial message

  • attach that implementation using Session#addMessageHandler methods (multiple combinations available)

addMessageHandler permutations

Couple of additional (overloaded) addMessageHandler methods were added to the Session interface as a part of WebSocket 1.1 release. In fact this was the only (minor) change in the 1.1 MR (maintenance release). For details, please check the change log

//attaching message handlers
public class ProgrammaticEndpoint extends Endpoint {
@Override
public void onOpen(Session session, EndpointConfig config) {
session.addMessageHandler(new WholeBinaryMsgHandler()); //basic
session.addMessageHandler(String.class, new WholeTextMsgHandler()); //specify class type for Whole message handler
session.addMessageHandler(ByteBuffer.class, new PartialBinaryMsgHandler()); //specify class type for Partial message handler
}
}

Other possible parameters for @OnMessage

In addition to the message itself, a method annotated with OnMessage can also receive the following information (which will be injected by the implementation at runtime)

  • zero or more String parameters annotated with @javax.websocket.PathParam (it is similar in spirit to the JAX-RS @javax.ws.rs.PathParam annotation)

  • an instance of Session

  • an instance of EndpointConfig (server or client side)

public void onMsgCallback(String theMsg, @PathParam("user") String username, Session peer, EndpointConfig condfig){
System.out.println("I have everything I could possibly receive from the WebSocket implementation !");
}

Handling Java primitives

A WebSocket implementation provides default decoders for Java primitive (int-Integer, long-Long, double-Double etc.) data types. It is possible to write a custom decoder for any of these in order to override the default ones

Up next

We'll explore the WebSocket Client API in detail