Java EE 7: Building Web Applications with WebSocket, JavaScript and HTML5
Overview
Purpose
This tutorial shows you how to create an application that uses the WebSocket API for real-time communication between a client and a server. You learn how to:
- Create a Java Platform, Enterprise Edition 7 (Java EE 7) application that uses the WebSocket API
- Use the
OnOpen
andOnMessage
WebSocket lifecycle events to perform different actions on the Java EE 7 application. - Define a client-side WebSocket endpoint by using JavaScript
- Operate on Plain Old Java Objects (POJOs), in real-time, with actions invoked from a web browser client
Time to Complete
Approximately 1 hour
Introduction
Modern web applications require more interactivity than ever before for client/server communications. HTTP, however, wasn't built to deliver the kind of interactivity needed today. "Push" or Comet techniques, such as long-polling, emerged as a way to allow a server to push data to a browser. Because these techniques usually rely on HTTP, they present some disadvantages for client/server communications, such as HTTP overhead. These disadvantages result in less efficient communication between the server and the web browser, especially for real-time applications.
WebSocket provides an alternative to this limitation by providing bi-directional, full-duplex, real-time, client/server communications. The server can send data to the client at any time. Because WebSocket runs over TCP, it also reduces the overhead of each message. WebSocket also provides greater scalability for message-intensive applications because only one connection per client is used (whereas HTTP creates one request per message). Finally, WebSocket is part of Java EE 7, so you can use other technologies in the Java EE 7 stack.
Scenario
In this tutorial, you create Java WebSocket Home, a smart home control web application based on Java EE 7. Java WebSocket Home has a user interface for connecting and controlling fictitious devices from a web browser to a Java application. This application provides real-time updates to all clients that are connected to the Java WebSocket Home server.
Software Requirements
The following is a list of software requirements needed for this tutorial:
- Download and install the Java EE 7 software development kit (SDK) from http://www.oracle.com/technetwork/java/javaee/downloads/index.html.
- Download and install the Java NetBeans 7.3.1 integrated development environment (IDE) from http://www.netbeans.org/downloads/index.html.
- Download and install Oracle GlassFish Server 4.0 from http://www.oracle.com/us/products/middleware/cloud-app-foundation/glassfish-server/overview/index.html.
Prerequisites
Before starting this tutorial, you should have:
- Knowledge of the Java programming language
- Basic knowledge of Java EE 7
- Basic knowledge of HTML 5, JavaScript, and cascading style sheets (CSS)
Introduction to the WebSocket API in Java EE 7
Introduced as part of the HTML 5 initiative, the WebSocket protocol is a standard web technology that simplifies communication and connection management between clients and a server. By maintaining a constant connection, WebSocket provides full-duplex client/server communication. It also provides a low-latency, low-level communication that works on the underlying TCP/IP connection.
The Java API for WebSocket (JSR-356) simplifies the integration of WebSocket into Java EE 7 applications.
Here are some of the features of the Java API for WebSocket:
- Annotation-driven programming that allows developers to use POJOs to interact with WebSocket lifecycle events
- Interface-driven programming that allows developers to implement interfaces and methods to interact with WebSocket lifecycle events
- Integration with other Java EE technologies (You can inject objects and Enterprise JavaBeans by using components such as Contexts and Dependency Injection.)
Creating a Java EE 7 Project
In this section, you create a Java EE 7 web application.
-
Open the NetBeans IDE.
-
From the File menu, select New Project.
-
In the New Project dialog box, perform the following steps:
- Select Java Web from Categories.
- Select Web Application from Projects.
- Click Next.
-
Enter WebsocketHome as the project name and click Next.
-
In the New Web Application dialog box, perform the following steps:
- Select GlassFish Server from the Server list.
- Enter WebsocketHome as the context path.
- Click Finish.
The
WebsocketHome
project has been created. -
Right-click the
WebsocketHome
project and select Run to test your application.A browser window displays a TODO write content message.
You successfully created a Java EE 7 web application by using NetBeans.
Creating the Device Model
In this section, you create the class that contains a device's attributes.
-
Select File > New File.
-
In the New File dialog box, perform the following steps:
- Select Java from Categories.
- Select Java Class from File Types.
- Click Next.
-
In the New Java Class dialog box, perform the following steps:
- Enter Device as the class name.
- Enter org.example.model as the package.
- Click Finish.
The
Device
class is added to the project. -
Add the following code to the Device.java class to define the class constructor, and its getter and setter methods:
package org.example.model; public class Device { private int id; private String name; private String status; private String type; private String description; public Device() { } public int getId() { return id; } public String getName() { return name; } public String getStatus() { return status; } public String getType() { return type; } public String getDescription() { return description; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setStatus(String status) { this.status = status; } public void setType(String type) { this.type = type; } public void setDescription(String description) { this.description = description; } }
-
Select File > Save to save the file.
You successfully created the Device
class.
Creating the WebSocket Server Endpoint
In this section, you create a WebSocket endpoint.
-
Select File > New File.
-
In the New File dialog box, perform the following steps:
- Select Java from Categories.
- Select Java Class from File Types.
- Click Next.
-
In the New Java Class dialog box, perform the following steps:
- Enter DeviceWebSocketServer as the class name.
- Enter org.example.websocket as the package.
- Click Finish.
The
DeviceWebSocketServer
class is added to the project. -
Define the WebSocket server endpoint path by adding the following code:
package org.example.websocket; import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/actions") public class DeviceWebSocketServer { }
-
Define the WebSocket lifecycle annotations by adding the following methods and imports to the
DeviceWebSocketServer
class:import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/actions") public class DeviceWebSocketServer { @OnOpen public void open(Session session) { } @OnClose public void close(Session session) { } @OnError public void onError(Throwable error) { } @OnMessage public void handleMessage(String message, Session session) { } }
The WebSocket lifecycle annotations are mapped to Java methods. In this example, the
@OnOpen
annotation is mapped to theopen()
method; the@OnMessage
annotation is mapped to thehandleMessage()
method; the@OnClose
annotation to theclose()
method; and the@OnError
annotation to theonError()
method. -
Specify that the class is application-scoped by adding the
@ApplicationScoped
annotation and importing its package.... import javax.websocket.OnOpen; import javax.websocket.Session; import javax.enterprise.context.ApplicationScoped; @ApplicationScoped @ServerEndpoint("/actions") public class DeviceWebSocketServer { ... }
-
Save the file.
You successfully created the WebSocket server endpoint.
Creating the Session Handler
In this section, you create a class for handling the sessions that are connected to the server.
-
Select File > New File.
-
In the New File dialog box, perform the following steps:
- Select Java from Categories.
- Select Java Class from File Types.
- Click Next.
-
In the New Java Class dialog box, perform the following steps:
- Enter DeviceSessionHandler as the class name.
- Enter org.example.websocket as the package.
- Click Finish.
The
DeviceSessionHandler
class is added to the project. -
Specify that the class is application-scoped by adding the
@ApplicationScoped
annotation and importing its corresponding package.package org.example.websocket; import javax.enterprise.context.ApplicationScoped; @ApplicationScoped public class DeviceSessionHandler { }
-
Declare a
HashSet
for storing the list of devices added to the application and the active sessions in the application, and import their packages.package org.example.websocket; import javax.enterprise.context.ApplicationScoped; import java.util.HashSet; import java.util.Set; import javax.websocket.Session; import org.example.model.Device; @ApplicationScoped public class DeviceSessionHandler { private final Set<Session> sessions = new HashSet<>(); private final Set<Device> devices = new HashSet<>(); }
Note: Each client connected to the application has its own session.
-
Define the following methods for adding and removing sessions to the server.
package org.example.websocket; ... @ApplicationScoped public class DeviceSessionHandler { ... public void addSession(Session session) { sessions.add(session); } public void removeSession(Session session) { sessions.remove(session); } }
-
Define the methods that operate on the
Device
object.These methods are:
addDevice()
- Add a device to the application.removeDevice()
- Remove a device from the application.toggleDevice()
- Toggle the device status.getDevices()
- Retrieve the list of devices and their attributes.getDeviceById()
- Retrieve a device with a specific identifier.createAddMessage()
- Build a JSON message for adding a device to the application.sendToSession()
- Send an event message to a client.sendToAllConnectedSessions()
- Send an event message to all connected clients.
package org.example.websocket; ... public class DeviceSessionHandler { ... public List<Device> getDevices() { return new ArrayList<>(devices); } public void addDevice(Device device) { } public void removeDevice(int id) { } public void toggleDevice(int id) { } private Device getDeviceById(int id) { return null; } private JsonObject createAddMessage(Device device) { return null; } private void sendToAllConnectedSessions(JsonObject message) { } private void sendToSession(Session session, JsonObject message) { } }
-
Save the file.
You successfully created the session handler.
Rendering the User Interface
In this section, you create the Java WebSocket Home user interface (UI) by using HTML5 and CSS.
-
Open the
index.html
file. -
Enter the following code to add the proper elements for adding and displaying devices on the web browser client.
<!DOCTYPE html> <html> <head> <title></title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <script src="websocket.js"></script> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <div id="wrapper"> <h1>Java Websocket Home</h1> <p>Welcome to the Java WebSocket Home. Click the Add a device button to start adding devices.</p> <br /> <div id="addDevice"> <div class="button"> <a href="#" OnClick="showForm()">Add a device</a> </div> <form id="addDeviceForm"> <h3>Add a new device</h3> <span>Name: <input type="text" name="device_name" id="device_name"></span> <span>Type: <select id="device_type"> <option name="type" value="Appliance">Appliance</option> <option name="type" value="Electronics">Electronics</option> <option name="type" value="Lights">Lights</option> <option name="type" value="Other">Other</option> </select></span> <span>Description:<br /> <textarea name="description" id="device_description" rows="2" cols="50"></textarea> </span> <input type="button" class="button" value="Add" onclick=formSubmit()> <input type="reset" class="button" value="Cancel" onclick=hideForm()> </form> </div> <br /> <h3>Currently connected devices:</h3> <div id="content"> </div> </div> </body> </html>
-
Save the file.
-
Select File > New File.
-
In the New File dialog dialog box, perform the following steps:
- Select Web from Categories.
- Select Cascading Style Sheet from File Types.
- Click Next.
-
In the New Cascading Style Sheet dialog box, perform the following steps:
- Enter style as the file name.
- Select Web Pages as the location.
- Click Finish.
The
style.css
file is added to the project. -
Copy the following code into the
style.css
file.body { font-family: Arial, Helvetica, sans-serif; font-size: 80%; background-color: #1f1f1f; } #wrapper { width: 960px; margin: auto; text-align: left; color: #d9d9d9; } p { text-align: left; } .button { display: inline; color: #fff; background-color: #f2791d; padding: 8px; margin: auto; border-radius: 8px; -moz-border-radius: 8px; -webkit-border-radius: 8px; box-shadow: none; border: none; } .button:hover { background-color: #ffb15e; } .button a, a:visited, a:hover, a:active { color: #fff; text-decoration: none; } #addDevice { text-align: center; width: 960px; margin: auto; margin-bottom: 10px; } #addDeviceForm { text-align: left; width: 400px; margin: auto; padding: 10px; } #addDeviceForm span { display: block; } #content { margin: auto; width: 960px; } .device { width: 180px; height: 110px; margin: 10px; padding: 16px; color: #fff; vertical-align: top; border-radius: 8px; -moz-border-radius: 8px; -webkit-border-radius: 8px; display: inline-block; } .device.off { background-color: #c8cccf; } .device span { display: block; } .deviceName { text-align: center; font-weight: bold; margin-bottom: 12px; } .removeDevice { margin-top: 12px; text-align: center; } .device.Appliance { background-color: #5eb85e; } .device.Appliance a:hover { color: #a1ed82; } .device.Electronics { background-color: #0f90d1; } .device.Electronics a:hover { color: #4badd1; } .device.Lights { background-color: #c2a00c; } .device.Lights a:hover { color: #fad232; } .device.Other { background-color: #db524d; } .device.Other a:hover { color: #ff907d; } .device a { text-decoration: none; } .device a:visited, a:active, a:hover { color: #fff; } .device a:hover { text-decoration: underline; }
-
Save the file.
You successfully created the UI elements for the Java WebSocket Home application.
Creating the WebSocket Client Endpoint
In this section, you specify the client endpoint by using JavaScript.
-
Select File > New File.
-
In the New File dialog box, perform the following steps:
- Select Web from Categories.
- Select JavaScript File from File Types.
- Click Next.
-
Enter websocket as the file name and click Finish.
The
websocket.js
file is added to the project.The file performs the following actions:
- Maps the WebSocket server endpoint to the URI defined in "Creating the WebSocket Server Endpoint".
- Captures the JavaScript events for adding, removing, and changing a device's status and pushes those events to the WebSocket server.
These methods are
addDevice()
,removeDevice()
, andtoggleDevice()
. The actions are sent in JSON messages to the WebSocket server. - Defines a callback method for the WebSocket
onmessage
event. Theonmessage
event captures the events sent from the WebSocket server (in JSON) and processes those actions. In this application, these actions are usually rendering changes in the client UI. - Toggles the visibility of an HTML form for adding a new device.
-
Add the following code to the
websocket.js
file.window.onload = init; var socket = new WebSocket("ws://localhost:8080/WebsocketHome/actions"); socket.onmessage = onMessage; function onMessage(event) { var device = JSON.parse(event.data); if (device.action === "add") { printDeviceElement(device); } if (device.action === "remove") { document.getElementById(device.id).remove(); //device.parentNode.removeChild(device); } if (device.action === "toggle") { var node = document.getElementById(device.id); var statusText = node.children[2]; if (device.status === "On") { statusText.innerHTML = "Status: " + device.status + " (<a href=\"#\" OnClick=toggleDevice(" + device.id + ")>Turn off</a>)"; } else if (device.status === "Off") { statusText.innerHTML = "Status: " + device.status + " (<a href=\"#\" OnClick=toggleDevice(" + device.id + ")>Turn on</a>)"; } } } function addDevice(name, type, description) { var DeviceAction = { action: "add", name: name, type: type, description: description }; socket.send(JSON.stringify(DeviceAction)); } function removeDevice(element) { var id = element; var DeviceAction = { action: "remove", id: id }; socket.send(JSON.stringify(DeviceAction)); } function toggleDevice(element) { var id = element; var DeviceAction = { action: "toggle", id: id }; socket.send(JSON.stringify(DeviceAction)); } function printDeviceElement(device) { var content = document.getElementById("content"); var deviceDiv = document.createElement("div"); deviceDiv.setAttribute("id", device.id); deviceDiv.setAttribute("class", "device " + device.type); content.appendChild(deviceDiv); var deviceName = document.createElement("span"); deviceName.setAttribute("class", "deviceName"); deviceName.innerHTML = device.name; deviceDiv.appendChild(deviceName); var deviceType = document.createElement("span"); deviceType.innerHTML = "<b>Type:</b> " + device.type; deviceDiv.appendChild(deviceType); var deviceStatus = document.createElement("span"); if (device.status === "On") { deviceStatus.innerHTML = "<b>Status:</b> " + device.status + " (<a href=\"#\" OnClick=toggleDevice(" + device.id + ")>Turn off</a>)"; } else if (device.status === "Off") { deviceStatus.innerHTML = "<b>Status:</b> " + device.status + " (<a href=\"#\" OnClick=toggleDevice(" + device.id + ")>Turn on</a>)"; //deviceDiv.setAttribute("class", "device off"); } deviceDiv.appendChild(deviceStatus); var deviceDescription = document.createElement("span"); deviceDescription.innerHTML = "<b>Comments:</b> " + device.description; deviceDiv.appendChild(deviceDescription); var removeDevice = document.createElement("span"); removeDevice.setAttribute("class", "removeDevice"); removeDevice.innerHTML = "<a href=\"#\" OnClick=removeDevice(" + device.id + ")>Remove device</a>"; deviceDiv.appendChild(removeDevice); } function showForm() { document.getElementById("addDeviceForm").style.display = ''; } function hideForm() { document.getElementById("addDeviceForm").style.display = "none"; } function formSubmit() { var form = document.getElementById("addDeviceForm"); var name = form.elements["device_name"].value; var type = form.elements["device_type"].value; var description = form.elements["device_description"].value; hideForm(); document.getElementById("addDeviceForm").reset(); addDevice(name, type, description); } function init() { hideForm(); }
-
Save the file.
You successfully created the WebSocket client endpoint and the defined actions for handling WebSocket events in the client.
Processing WebSocket Events in the Server
In this section, you process WebSocket lifecycle events in the DeviceWebSocketServer
class.
-
Open the
DeviceWebSocketServer
class. -
Inject a
DeviceSessionHandler
object to process the WebSocket lifecycle events in each session and import its corresponding package.package org.example.websocket; ... import javax.websocket.server.ServerEndpoint; import javax.inject.Inject; @ApplicationScoped @ServerEndpoint("/actions") public class DeviceWebSocketServer { @Inject private DeviceSessionHandler sessionHandler; @OnOpen public void open(Session session) { } ... }
-
Process the
OnMessage
WebSocket lifecycle event by adding the following code to the open method.@OnOpen public void open(Session session) { sessionHandler.addSession(session); }
The
OnMessage
method performs the following actions:- Reads device actions and attributes sent from the client.
- Invokes the session handler to perform the proper operations on the specified
Device
object. In this application, the add action sent from the client invokes theaddDevice
method, the remove action invokes theremoveDevice
method, and the toggle action invokes thetoggleDevice
method.
-
Process the
OnOpen
WebSocket event and add the missing imports.package org.example.websocket; ... import java.io.StringReader; import javax.json.Json; import javax.json.JsonObject; import javax.json.JsonReader; import org.example.model.Device; ... @OnMessage public void handleMessage(String message, Session session) { try (JsonReader reader = Json.createReader(new StringReader(message))) { JsonObject jsonMessage = reader.readObject(); if ("add".equals(jsonMessage.getString("action"))) { Device device = new Device(); device.setName(jsonMessage.getString("name")); device.setDescription(jsonMessage.getString("description")); device.setType(jsonMessage.getString("type")); device.setStatus("Off"); sessionHandler.addDevice(device); } if ("remove".equals(jsonMessage.getString("action"))) { int id = (int) jsonMessage.getInt("id"); sessionHandler.removeDevice(id); } if ("toggle".equals(jsonMessage.getString("action"))) { int id = (int) jsonMessage.getInt("id"); sessionHandler.toggleDevice(id); } } }
The
OnOpen
event reads the attributes sent from the client in JSON and creates a newDevice
object with the specified parameters. -
Implement the
OnClose
andOnError
actions and add the missing imports.package org.example.websocket; ... import java.util.logging.Level; import java.util.logging.Logger; ... @OnClose public void close(Session session) { sessionHandler.removeSession(session); } @OnError public void onError(Throwable error) { Logger.getLogger(DeviceWebSocketServer.class.getName()).log(Level.SEVERE, null, error); }
-
Save the file.
You successfully processed WebSocket lifecycle events in the DeviceWebSocketServer
class.
Implementing the WebSocket Actions in the Session Handler
In this section, you perform operations in the Device
object by using the DeviceSessionHandler
class.
-
Open the
DeviceSessionHandler.java
class. -
Define a variable for storing the device identifiers in the server.
... public class DeviceSessionHandler { private int deviceId = 0; private final Set<Session> sessions = new HashSet<>(); private final Set<Device> devices = new HashSet<>(); ... }
-
Add the
for
loop to theaddSession
method to send the list of devices to the connected client.public void addSession(Session session) { sessions.add(session); for (Device device : devices) { JsonObject addMessage = createAddMessage(device); sendToSession(session, addMessage); } }
-
Implement the
addDevice
method by adding the following code.public void addDevice(Device device) { device.setId(deviceId); devices.add(device); deviceId++; JsonObject addMessage = createAddMessage(device); sendToAllConnectedSessions(addMessage); }
The
addDevice
method performs the following actions:- Creates a new
Device
object with the current value of thedeviceID
variable and the parameters specified by the user in the client. - Sends a message, in JSON, to all sessions or active clients in the WebSocket server.
- Creates a new
-
Implement the
removeDevice
method.public void removeDevice(int id) { Device device = getDeviceById(id); if (device != null) { devices.remove(device); JsonProvider provider = JsonProvider.provider(); JsonObject removeMessage = provider.createObjectBuilder() .add("action", "remove") .add("id", id) .build(); sendToAllConnectedSessions(removeMessage); } }
The
removeDevice
method removes the device object specified by the user and sends a message, in JSON, to all sessions that are active in the WebSocket server. -
Implement the
toggleDevice
method.public void toggleDevice(int id) { JsonProvider provider = JsonProvider.provider(); Device device = getDeviceById(id); if (device != null) { if ("On".equals(device.getStatus())) { device.setStatus("Off"); } else { device.setStatus("On"); } JsonObject updateDevMessage = provider.createObjectBuilder() .add("action", "toggle") .add("id", device.getId()) .add("status", device.getStatus()) .build(); sendToAllConnectedSessions(updateDevMessage); } }
The
toggleDevice
method toggles the device status and sends the event to all sessions that are still active in the WebSocket server. -
Implement missing methods.
private Device getDeviceById(int id) { for (Device device : devices) { if (device.getId() == id) { return device; } } return null; } private JsonObject createAddMessage(Device device) { JsonProvider provider = JsonProvider.provider(); JsonObject addMessage = provider.createObjectBuilder() .add("action", "add") .add("id", device.getId()) .add("name", device.getName()) .add("type", device.getType()) .add("status", device.getStatus()) .add("description", device.getDescription()) .build(); return addMessage; } private void sendToAllConnectedSessions(JsonObject message) { for (Session session : sessions) { sendToSession(session, message); } } private void sendToSession(Session session, JsonObject message) { try { session.getBasicRemote().sendText(message.toString()); } catch (IOException ex) { sessions.remove(session); Logger.getLogger(DeviceSessionHandler.class.getName()).log(Level.SEVERE, null, ex); } }
-
Save the file.
You successfully implemented the WebSocket actions in the session handler.
Testing the Java WebSocket Home Application
In this section, you test the Java WebSocket Home application.
-
Right-click the
WebsocketHome
project and click Run to build and deploy the project.A Web browser displays the Java WebSocketHome index page.
-
Open another web browser and place it next to the first one.
-
In either window, click Add a device to display the "Add a new device" form.
-
In the "Add a new device form," perform the following steps:
- Enter Microwave as the name.
- Select Appliance as the type.
- Enter Kitchen as the description.
- Click Add.
A device is added to the Java WebSocket Home server and it is rendered in both web browsers.
-
Optional: Add more devices of different types.
-
On any device, click Turn on.
The device status changes to Off in the server and all clients.
-
On any device, click Remove device.
The device is removed from the server and all clients.
Summary
Congratulations! You created a smart home control web application by using Java EE 7 and the WebSocket API.
You also learned how to:
- Define a WebSocket server endpoint in a Java class by using the WebSocket API annotations
- Send messages to and from the client to the WebSocket server in JSON
- Use the WebSocket API lifecycle annotations to handle WebSocket events
- Process WebSocket lifecycle events in the client by using HTML5 and JavaScript
Resources
For more information about the topics in this tutorial, see:
- Java EE 7: New Features, an Oracle-instructed course on the new features of Java EE 7
- Java EE 7 Tutorial
- The Java EE 7 Tutorial chapter on WebSocket
- Using WebSocket for Real-Time Communication in Java Platform, Enterpise Edition 7, a tutorial about WebSocket for Java EE 7.
- The Aquarium Blog
- JSR 356: Java API for WebSocket
- WebSocket.org, a WebSocket community
- "Java EE7 - Embracing HTML5" article in Java Magazine
- To learn more about Java EE 7, refer to additional OBEs in the Oracle Learning Library.
Credits
- Lead Curriculum Developer: Miguel Salazar
- Other Contributors: Eduardo Moranchel
To navigate this Oracle by Example tutorial, note the following:
- Topic List:
- Click a topic to navigate to that section.
- Expand All Topics:
- Click the button to show or hide the details for the sections. By default, all topics are collapsed.
- Hide All Images:
- Click the button to show or hide the screenshots. By default, all images are displayed.
- Print:
- Click the button to print the content. The content that is currently displayed or hidden is printed.
To navigate to a particular section in this tutorial, select the topic from the list.