Since Java 8 was released, lots of new or reworked APIs emerged, simply because Java 8 is really evolutionary step in Java language specification and it is definitely worth to update the APIs to enable newly added features.

Unfortunately, Java EE APIs have different release cycle and we’d need to wait a little bit for next version, but I was able to add at least something to Tyrus, which is reference implementation of JSR 356 – WebSocket API for Java. There are some other changes which could be made, but most of them require changes in the API itself, which we cannot do at the moment, so I focused on other features which do enhance the experience. You can view this as the “first” attempt to bring some Java 8 API features into the existing programming model.

The area covered in this post is Client API – mainly because this change can be used easily with any WebSocket API implementation; it is built on top of existing API. If I would be thorough, I’d apply this to deployment of the programmatic endpoints on the server side as well, but that would be far more work and since the server side usually uses annotated endpoints instead of programmatic ones, I believe this should be sufficient to evaluate the proposal and provide any feedback you might have.

The client API was originally crafted as something, which should be similar to the JavaScript counterpart, but the language differences at that time diminished that effort to basically just naming and few other details (and the alignment with server side API had higher priority). Anyway, I believe it is the time, when we should reconsider Client API (and programmatic model in general).

If you look at javascript API, it’s basically just one object, to which you can set handlers for some events:

1 2 3 4 5 6 window.websocket = new WebSocket(window.wsUrl( "/sample-btc-xchange/market" )); var websocket = window.websocket; websocket.onopen = function () { /* ... */ }; websocket.onmessage = function (evt) { /* ... */ }; websocket.onclose = function () { /* ... */ }; websocket.onerror = function () { * ... */ };

The current Java version of this would be:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 client.connectToServer( new Endpoint() { @Override public void onOpen(Session session, EndpointConfig EndpointConfig) { try { session.addMessageHandler( new MessageHandler.Whole() { @Override public void onMessage(String message) { }); } } } @Override public void onClose(Session session, CloseReason closeReason) { } }, ClientEndpointConfig.Builder.create().build(), getURI(EchoEndpoint. class ));

which does not seem nice to me, additionally, since Lambdas and Method References were added to the language, we could do much better than that. The main idea behind these changes is that programmatic endpoint (class javax.websocket.Endpoint) is no longer necessary, since it can be replaced by three method references, especially when you usually don’t need all of them. And Java 8 offers nice predefined Functional Interfaces, which work just fine, so we don’t even need to introduce declaration of those, we only need to define consumed types.

With these in mind, the previous code could look like:

1 2 3 4 Session session = new SessionBuilder().uri(getURI(SessionBuilderTestEndpoint. class )) .messageHandler(String. class , message -> { }) .onClose((session, closeReason) -> { }) .connect();

The main advantage I see here (other than readability) is that the configuration is no longer in two places – programmatic connectToServer method requires Endpoint, ClientEndpointConfig and other parameters, but it does not allow to register MessageHandler right away – user needs to implement that logic in @OnOpen method. On the other hand, proposed approach does that for you, so @OnOpen implementation in this case will be only what it is supposed to be. And I believe it could be omitted in lots of cases, which also contributes to readability and ease of use. Also, last but not least, less code written, less bugs introduced..

Previous code sample included lambdas, but sometimes, you might want to have more complicated code there, which does not look good implemented as lambda. In that case, you can declare standard methods with appropriate parameters and provide Method Reference instead. Slightly more complex sample would then look like:

1 2 3 4 5 6 Session s = new SessionBuilder().uri(getURI(SessionBuilderTestEndpoint. class )) .onOpen( this ::onOpen) .messageHandler(String. class , this ::onMessage) .onError( this ::onError) .onClose( this ::onClose) .connect();

More code examples can be seen in SessionBuilderTest class.

You are welcomed to try and give us your feedback. The SessionBuilder is in module “org.glassfish.tyrus.ext:tyrus-client-java8″, which is currently released only as snapshot (should be part of next Tyrus release), so you’ll need to add reference to maven.java.net snapshot repository: https://maven.java.net/content/repositories/snapshots/

Complete dependencies and repo declaration in pom.xml might look like:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 org.glassfish.tyrus.ext tyrus-client-java8 1.11-SNAPSHOT org.glassfish.tyrus tyrus-container-grizzly-client 1.11-SNAPSHOT maven.java.net-snapshots https://maven.java.net/content/repositories/snapshots/

Any feedback appreciated!

Links