What is WebSocket?
Think of WebSocket like a phone call vs regular mail:
- 📬 HTTP = Mail (request → wait → response)
- 📞 WebSocket = Phone call (continuous two-way conversation)
- ⚡ Real-time updates without polling
- 💬 Perfect for chat, notifications, live updates
WebSocket provides full-duplex communication over a single TCP connection. Once established, both client and server can send messages anytime without requesting.
Use cases: Chat apps, live notifications, multiplayer games, stock tickers, collaborative tools
HTTP vs WebSocket
// HTTP - Request/Response (one-way)
Client → Request → Server
Client ← Response ← Server
// Connection closes. Need new request for more data.
// WebSocket - Persistent Connection (two-way)
Client ←→ Server
// Connection stays open. Both can send anytime!
Simple WebSocket Server
import jakarta.websocket.*;
import jakarta.websocket.server.*;
@ServerEndpoint("/chat") // ws://localhost:8080/myapp/chat
public class ChatEndpoint {
@OnOpen // Called when client connects
public void onOpen(Session session) {
System.out.println("New connection: " + session.getId());
}
@OnMessage // Called when message received
public String onMessage(String message, Session session) {
System.out.println("Received: " + message);
return "Echo: " + message; // Send back to sender
}
@OnClose // Called when connection closes
public void onClose(Session session, CloseReason reason) {
System.out.println("Connection closed: " + reason.getReasonPhrase());
}
@OnError // Called on error
public void onError(Session session, Throwable throwable) {
System.err.println("Error: " + throwable.getMessage());
}
}
JavaScript Client
// Connect to WebSocket
const socket = new WebSocket("ws://localhost:8080/myapp/chat");
// Connection opened
socket.onopen = function(event) {
console.log("Connected!");
socket.send("Hello Server!");
};
// Message received
socket.onmessage = function(event) {
console.log("Server says: " + event.data);
};
// Connection closed
socket.onclose = function(event) {
console.log("Disconnected");
};
// Error occurred
socket.onerror = function(error) {
console.error("Error: ", error);
};
Broadcasting to All Clients
import java.util.*;
import java.util.concurrent.*;
@ServerEndpoint("/chat")
public class ChatRoom {
// Store all connected sessions
private static Set<Session> sessions =
Collections.newSetFromMap(new ConcurrentHashMap<>());
@OnOpen
public void onOpen(Session session) {
sessions.add(session);
System.out.println("User joined. Total users: " + sessions.size());
// Notify all users
broadcast("A new user joined the chat!");
}
@OnMessage
public void onMessage(String message, Session session) {
// Send message to all connected users
broadcast(session.getId() + ": " + message);
}
@OnClose
public void onClose(Session session) {
sessions.remove(session);
System.out.println("User left. Total users: " + sessions.size());
broadcast("A user left the chat");
}
// Broadcast to all connected clients
private static void broadcast(String message) {
for (Session session : sessions) {
if (session.isOpen()) {
try {
session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
Path Parameters
@ServerEndpoint("/chat/{room}") // ws://localhost:8080/myapp/chat/general
public class MultiRoomChat {
@OnOpen
public void onOpen(Session session,
@PathParam("room") String roomName) {
System.out.println("User joined room: " + roomName);
// Store room name in session
session.getUserProperties().put("room", roomName);
}
@OnMessage
public void onMessage(String message, Session session) {
String room = (String) session.getUserProperties().get("room");
System.out.println("Message in " + room + ": " + message);
}
}
Sending to Specific Client
@ServerEndpoint("/notification")
public class NotificationEndpoint {
private static Map<String, Session> userSessions = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session) {
// Get username from query parameter
String username = session.getRequestParameterMap()
.get("username")
.get(0);
userSessions.put(username, session);
System.out.println(username + " connected");
}
// Send notification to specific user
public static void notifyUser(String username, String message) {
Session session = userSessions.get(username);
if (session != null && session.isOpen()) {
try {
session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
// Usage from anywhere in your app
NotificationEndpoint.notifyUser("alice", "New message from Bob!");
JSON Messages
Message Class
public class ChatMessage {
private String sender;
private String content;
private long timestamp;
// Constructors, getters, setters
}
Endpoint with JSON
import jakarta.json.bind.*;
@ServerEndpoint("/chat")
public class JsonChatEndpoint {
private static Jsonb jsonb = JsonbBuilder.create();
@OnMessage
public void onMessage(String jsonMessage) {
// Parse JSON to object
ChatMessage message = jsonb.fromJson(jsonMessage, ChatMessage.class);
System.out.println(message.getSender() + ": " + message.getContent());
// Create response
ChatMessage response = new ChatMessage();
response.setSender("Server");
response.setContent("Message received!");
response.setTimestamp(System.currentTimeMillis());
// Send as JSON
String jsonResponse = jsonb.toJson(response);
broadcast(jsonResponse);
}
}
JavaScript Client with JSON
const socket = new WebSocket("ws://localhost:8080/myapp/chat");
socket.onopen = function() {
// Send JSON message
const message = {
sender: "Alice",
content: "Hello everyone!",
timestamp: Date.now()
};
socket.send(JSON.stringify(message));
};
socket.onmessage = function(event) {
// Parse JSON response
const message = JSON.parse(event.data);
console.log(message.sender + ": " + message.content);
};
CDI Integration
@ServerEndpoint(
value = "/chat",
configurator = CdiAwareConfigurator.class // Enable CDI
)
public class CdiChatEndpoint {
@Inject // Inject services!
private MessageService messageService;
@Inject
private UserService userService;
@OnMessage
public void onMessage(String message, Session session) {
// Use injected services
messageService.saveMessage(message);
String username = userService.getCurrentUser(session);
broadcast(username + ": " + message);
}
}
// Configurator to enable CDI
public class CdiAwareConfigurator extends ServerEndpointConfig.Configurator {
@Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
return CDI.current().select(endpointClass).get();
}
}
Timeouts and Idle Sessions
@ServerEndpoint("/chat")
public class TimeoutEndpoint {
@OnOpen
public void onOpen(Session session) {
// Set max idle timeout (15 minutes)
session.setMaxIdleTimeout(15 * 60 * 1000); // milliseconds
// Set max message size (1MB)
session.setMaxTextMessageBufferSize(1024 * 1024);
System.out.println("Session configured with timeout: " +
session.getMaxIdleTimeout());
}
@OnMessage
public void onMessage(String message, Session session) {
// Reset idle timeout on activity
session.setMaxIdleTimeout(15 * 60 * 1000);
}
}
Ping/Pong (Heartbeat)
@ServerEndpoint("/heartbeat")
public class HeartbeatEndpoint {
@OnMessage
public void onPong(PongMessage pong, Session session) {
System.out.println("Received pong from: " + session.getId());
}
// Send ping periodically to keep connection alive
public static void sendPing(Session session) {
if (session.isOpen()) {
try {
session.getBasicRemote().sendPing(ByteBuffer.wrap("ping".getBytes()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Client-Side Heartbeat
const socket = new WebSocket("ws://localhost:8080/myapp/heartbeat");
// Send ping every 30 seconds
setInterval(() => {
if (socket.readyState === WebSocket.OPEN) {
socket.send("ping");
}
}, 30000);
Best Practices
✅ DO:
- Use JSON for structured data - Easier to parse and extend
- Implement heartbeat/ping-pong - Detect dead connections
- Set timeouts - Clean up idle connections
- Handle errors gracefully - Connection can drop anytime
- Use ConcurrentHashMap for sessions - Thread-safe
- Close connections properly - Free resources
- Validate messages - Don't trust client data
- Use async sending for broadcasts - Better performance
❌ DON'T:
- Don't use WebSocket for everything - REST is often simpler
- Don't send huge messages - Split large data
- Don't forget authentication - Secure your endpoints
- Don't block the thread - Use async for long operations
- Don't forget to remove closed sessions - Memory leaks
- Don't store state in endpoint - Use session attributes
Summary
- WebSocket provides real-time bidirectional communication
- @ServerEndpoint: Marks WebSocket endpoint
- @OnOpen: Called when client connects
- @OnMessage: Called when message received
- @OnClose: Called when connection closes
- @OnError: Called on error
- Session: Represents connection to a client
- Broadcasting: Send to all connected clients
- JSON support: Send/receive structured data
- Use cases: Chat, live notifications, multiplayer games, real-time dashboards