JSR 356, Java API for WebSocket
Join the DZone community and get the full member experience.
Join For Freefor many web-based client-server applications, the old http request-response model has its limitations. information has to be transmitted from the server to the client in between requests, rather than upon request only.
a number of "hacks" have been used in the past to circumvent this problem, for example, long polling and comet. however, the need for a standards-based, bidirectional and full-duplex channel between clients and a server has only increased.
in 2011, the ietf standardized the websocket protocol as rfc 6455. since then, the majority of the web browsers are implementing client apis that support the websocket protocol. also, a number of java libraries have been developed that implement the websocket protocol.
the websocket protocol leverages the http upgrade technology to upgrade an http connection to a websocket. once it is upgraded, the connection is capable of sending messages (data frames) in both directions, independent of each other (full duplex). no headers or cookies are required, which considerably lowers the required bandwidth. typically, websockets are used to periodically send small messages (for example, a few bytes). additional headers would often make the overhead larger than the payload.
jsr 356
jsr 356, java api for websocket, specifies the api that java developers can use when they want to integrate websockets into their applications—both on the server side as well as on the java client side. every implementation of the websocket protocol that claims to be compliant with jsr 356 must implement this api. as a consequence, developers can write their websocket-based applications independent of the underlying websocket implementation. this is a huge benefit, because it prevents a vendor-lock and allows for more choices and freedom of libraries and application servers.
jsr 356 is a part of the upcoming java ee 7 standard; hence, all java ee 7–compliant application servers will have an implementation of the websocket protocol that adheres to the jsr 356 standard. once they are established, websocket client and server peers are symmetrical. the difference between a client api and a server api is, therefore, minimal. jsr 356 defines a java client api as well, which is a subset of the full api required in java ee 7.
a client-server application leveraging websockets typically contains a server component and one or more client components, as shown in figure 1:

figure 1
in this example, the server application is written in java, and the websocket protocol details are handled by the jsr 356 implementation contained in the java ee 7 container.
a javafx client can rely on any jsr 356–compliant client implementation for handling the websocket-specific protocol issues. other clients (for example, an ios client and an html5 client) can use other (non-java) implementations that are compliant with rfc 6455 in order to communicate with the server application.
programming model
the expert group that defined jsr 356 wanted to support patterns and techniques that are common to java ee developers. as a consequence, jsr 356 leverages annotations and injection.
in general, two different programming models are supported:
- annotation-driven. using annotated pojos, developers can interact with the websocket lifecycle events.
-
interface-driven. developers can implement the
endpoint
interface and the methods that interact with the lifecycle events.
lifecycle events
the typical lifecycle event of a websocket interaction goes as follows:
- one peer (a client) initiates the connection by sending an http handshake request.
- the other peer (the server) replies with a handshake response.
- the connection is established. from now on, the connection is completely symmetrical.
- both peers send and receive messages.
- one of the peers closes the connection.
most of the websocket lifecycle events can be mapped to java methods, both in the annotation-driven and interface-driven approaches.
annotation-driven approach
an endpoint that is accepting incoming websocket requests can be a pojo annotated with the
@serverendpoint
annotation. this annotation tells the container that the given class should be considered to be a websocket endpoint. the required
value
element specifies the path of the websocket endpoint.
consider the following code snippet:
@clientendpoint public class myclientendpoint {}
initiating a websocket connection in java leveraging the annotation-driven pojo approach can be done as follows:
javax.websocket.websocketcontainer container = javax.websocket.containerprovider.getwebsocketcontainer(); container.conntecttoserver(myclientendpoint.class, new uri("ws://localhost:8080/tictactoeserver/endpoint"));
hereafter, classes annotated with
@serverendpoint
or
@clientendpoint
will be called annotated endpoints.
once a websocket connection has been established, a
session
is created and the method annotated with
@onopen
on the annotated endpoint will be called. this method can contain a number of parameters:
-
a
javax.websocket.session
parameter, specifying the createdsession
-
an
endpointconfig
instance containing information about the endpoint configuration -
zero or more string parameters annotated with
@pathparam
, referring to path parameters on the endpoint path
the following method implementation will print the identifier of the session when a websocket is "opened":
@onopen public void myonopen (session session) { system.out.println ("websocket opened: "+session.getid()); }
a
session
instance is valid as long as the websocket is not closed. the
session
class contains a number of interesting methods that allow developers to
obtain more information about the connection. also, the
session
contains a hook to application-specific data, by means of the
getuserproperties()
method returning a
map<string, object>
. this allows developers to populate
session
instances with session- and application-specific information that should be shared among method invocations.
when the websocket endpoint receives a message, the method annotated with
@onmessage
will be called. a method annotated with
@onmessage
can contain the following parameters:
-
the
javax.websocket.session
parameter. -
zero or more string parameters annotated with
@pathparam
, referring to path parameters on the endpoint path. - the message itself. see below for an overview of possible message types.
when a text message has been sent by the other peer, the content of the message will be printed by the following code snippet:
@onmessage public void myonmessage (string txt) { system.out.println ("websocket received message: "+txt); } if the return type of the method annotated with @onmessage is not void, the websocket implementation will send the return value to the other peer. the following code snippet returns the received text message in capitals back to the sender: @onmessage public string myonmessage (string txt) { return txt.touppercase(); }
another way of sending messages over a websocket connection is shown below:
remoteendpoint.basic other = session.getbasicremote(); other.sendtext ("hello, world");
in this approach, we start from the
session
object, which can be obtained from the lifecycle callback methods (for example, the method annotated with
@onopen
). the
getbasicremote()
method on the
session
instance returns a representation of the other part of the websocket, the
remoteendpoint
. that
remoteendpoint
instance can be used for sending text or other types of messages, as described below.
when the websocket connection is closing, the method annotated with
@onclose
is called. this method can take the following parameters:
-
the
javax.websocket.session
parameter. note that this parameter cannot be used once the websocket is really closed, which happens after the@onclose
annotated method returns. -
a
javax.websocket.closereason
parameter describing the reason for closing the websocket, for example, normal closure, protocol error, overloaded service, and so on. -
zero or more string parameters annotated with
@pathparam
, referring to path parameters on the endpoint path.
the following code snippet will print the reason why a websocket is closing:
@onclose public void myonclose (closereason reason) { system.out.prinlnt ("closing a websocket due to "+reason.getreasonphrase()); }
to be complete, there is one more lifecycle annotation: in case an error is received, the method annotated with
@onerror
will be called.
interface-driven approach
the
annotation-driven approach allows us to annotate a java class and
methods with lifecycle annotations. using the interface-driven approach,
a developer extends
javax.websocket.endpoint
and overrides the
onopen
,
onclose
, and
onerror
methods:
public class myownendpoint extends javax.websocket.endpoint { public void onopen(session session, endpointconfig config) {...} public void onclose(session session, closereason closereason) {...} public void onerror (session session, throwable throwable) {...} }
in order to intercept messages, a
javax.websocket.messagehandler
needs to be registered in the
onopen
implementation:
public void onopen (session session, endpointconfig config) { session.addmessagehandler (new messagehandler() {...}); }
messagehandler
is an interface with two subinterfaces:
messagehandler.partial
and
messagehandler.whole
. the
messagehandler.partial
interface should be used when the developer wants to be notified about
partial deliveries of messages, and an implementation of
messagehandler.whole
should be used for notification about the arrival of a complete message.
the following code snippet listens to incoming text messages and sends the uppercase version of the text message back to the other peer:
public void onopen (session session, endpointconfig config) { final remoteendpoint.basic remote = session.getbasicremote(); session.addmessagehandler (new messagehandler.whole<string>() { public void onmessage(string text) { try { remote.sendstring(text.touppercase()); } catch (ioexception ioe) { // handle send failure here } } }); }
message types, encoders, and decoders
the java api for websocket is very powerful, because it allows any java object to be sent or received as a websocket message.
basically, there are three different types of messages:
- text-based messages
- binary messages
- pong messages, which are about the websocket connection itself
when using the interface-driven model, each session can register at most one
messagehandler
for each of these three different types of messages.
when using the annotation-driven model, for each different type of message, one
@onmessage
annotated method is allowed. the allowed parameters for specifying the
message content in the annotated methods are dependent on the type of
the message.
the
javadoc for the
@onmessage
annotation
clearly specifies the allowed message parameters based on the message type (the following is quoted from the javadoc):
-
"if the method is handling text messages:
-
string
to receive the whole message - java primitive or class equivalent to receive the whole message converted to that type
-
string
and boolean pair to receive the message in parts -
reader
to receive the whole message as a blocking stream -
any object parameter for which the endpoint has a text decoder (
decoder.text
ordecoder.textstream
).
-
-
if the method is handling binary messages:
-
byte[]
orbytebuffer
to receive the whole message -
byte[]
and boolean pair, orbytebuffer
and boolean pair to receive the message in parts -
inputstream
to receive the whole message as a blocking stream -
any object parameter for which the endpoint has a binary decoder (
decoder.binary
ordecoder.binarystream
).
-
-
if the method is handling pong messages:
-
pongmessage
for handling pong messages"
-
any java object can be encoded into a text-based or binary message using an encoder. this text-based or binary message is transmitted to the other peer, where it can be decoded into a java object again—or it can be interpreted by another websocket library. often, xml or json is used for the transmission of websocket messages, and the encoding/decoding then comes down to marshaling a java object into xml or json and back.
an encoder is defined as an implementation of the
javax.websocket.encoder
interface, and a decoder is an implementation of the
javax.websocket.decoder
interface. somehow, the endpoint instances need to know what the
possible encoders and decoders are. using the annotation-driven
approach, a list of encoders and decoders is passed via the encoder and
decoder elements in the
@clientendpoint
and
@serverendpoint
annotations.
the code in listing 1 shows how to register a
messageencoder
class that defines the conversion of an instance of
myjavaobject
to a text message. a
messagedecoder
class is registered for the opposite conversion.
@serverendpoint(value="/endpoint", encoders = messageencoder.class, decoders= messagedecoder.class) public class myendpoint { ... } class messageencoder implements encoder.text<myjavaobject> { @override public string encode(myjavaobject obj) throws encodingexception { ... } } class messagedecoder implements decoder.text<myjavaobject> { @override public myjavaobject decode (string src) throws decodeexception { ... } @override public boolean willdecode (string src) { // return true if we want to decode this string into a myjavaobject instance } }
listing 1
the
encoder
interface has a number of subinterfaces:
-
encoder.text
for converting java objects into text messages -
encoder.textstream
for adding java objects to a character stream -
encoder.binary
for converting java objects into binary messages -
encoder.binarystream
for adding java objects to a binary stream
similarly, the
decoder
interface has four subinterfaces:
-
decoder.text
for converting a text message into a java object -
decoder.textstream
for reading a java object from a character stream -
decoder.binary
for converting a binary message into a java object -
decoder.binarystream
for reading a java object from a binary stream
conclusion
the java api for websocket provides java developers with a standard api to integrate with the ietf websocket standard. by doing so, web clients or native clients leveraging any websocket implementation can easily communicate with a java back end.
the java api is highly configurable and flexible, and it allows java developers to use their preferred patterns.
see also
- jsr 356 on jcp.org
- jsr 356 specification work on java.net
- tyrus (jsr 356 reference implementation) on java.net
- jsr 342, the java ee 7 umbrella specification on jcp.org
- glassfish, the reference implementation for java ee
about the author
johan vos started working with java in 1995. he is a cofounder of lodgon, where he is working on java-based solutions for social networking software. an enthusiast of both embedded and enterprise development, vos focuses on end-to-end java using javafx and java ee. learn more at his blog or follow him at http://twitter.com/johanvos .
Published at DZone with permission of Johan Vos, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Replacing Apache Hive, Elasticsearch, and PostgreSQL With Apache Doris
-
Structured Logging
-
Scaling Site Reliability Engineering (SRE) Teams the Right Way
-
Managing Data Residency, the Demo
Comments