Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
145: WebSockets in Java
One of the first things you'll notice when moving from standard REST APIs to WebSockets is that the mental model shifts from "request-response" to "persistent connection." In a REST call, the server does its job and then forgets the client exists. With WebSockets, the server has to maintain a living relationship with every connected user. This is where things usually start breaking.
I recently saw a developer struggle with a real-time notification system. Their code looked perfect in isolation, but the moment they had more than two people connected, the server started throwing ConcurrentModificationException and IOException errors. Let's look at the broken code.
@ServerEndpoint("/notifications")
public class NotificationServer {
// A simple list to keep track of who is connected
private static List<Session> sessions = new ArrayList<>();
@OnOpen
public void onOpen(Session session) {
sessions.add(session);
}
@OnMessage
public void onMessage(String message, Session session) {
// Broadcast the message to everyone
for (Session s : sessions) {
s.getBasicRemote().sendText("User said: " + message);
}
}
@OnClose
public void onClose(Session session) {
sessions.remove(session);
}
}
The Broadcast Crash
If you run this with a single user, it works. But imagine this: the onMessage method is iterating through the sessions list to broadcast a message. While that loop is running, another user closes their browser tab, triggering onClose. The onClose method calls sessions.remove(session).
Because ArrayList is not thread-safe and doesn't allow modification while iterating, the server crashes with a ConcurrentModificationException. Even if you fixed the list, you'd eventually hit an IOException because you're trying to send a message to a Session that the client has already killed on their end. It's a classic "state management" nightmare.
Thread-Safe Session Management
To fix this, we need to handle the session list concurrently and verify the connection state before we attempt to push data. I prefer using a CopyOnWriteArrayList for small-to-medium sets of connections because it creates a fresh copy of the underlying array whenever the list is modified, making iteration safe from concurrent changes.
@ServerEndpoint("/notifications")
public class NotificationServer {
// Use a thread-safe collection for session management
private static final CopyOnWriteArrayList<Session> sessions = new CopyOnWriteArrayList<>();
@OnOpen
public void onOpen(Session session) {
sessions.add(session);
}
@OnMessage
public void onMessage(String message, Session session) {
for (Session s : sessions) {
// ALWAYS check if the session is still open before sending
if (s.isOpen()) {
try {
s.getBasicRemote().sendText("User said: " + message);
} catch (IOException e) {
// Log the error and potentially clean up the session
e.printStackTrace();
}
}
}
}
@OnClose
public void onClose(Session session) {
sessions.remove(session);
}
}
Understanding the Full-Duplex Flow
The key takeaway here is that @ServerEndpoint turns your Java class into a handler for a persistent pipe. Unlike an HTTP controller, the Session object is your lifeline. You can store user IDs in the session's user properties using session.getUserProperties().put("userId", id), which allows you to target specific users instead of just broadcasting to everyone.
One quick tip: getBasicRemote() is synchronous. If you're sending huge amounts of data or dealing with slow clients, it can block your execution thread. In high-throughput production systems, you'll want to look into getAsyncRemote(), which lets you send messages without waiting for the network ACK, though it requires a bit more care with callbacks.
📋 Practical Task
Build a Real-Time Stock Price Broadcaster
Your task is to create a WebSocket server that simulates a live stock ticker. Instead of waiting for a client message to trigger a response, the server should push updates autonomously.
- Create a
@ServerEndpoint("/stocks")class. - Maintain a thread-safe collection of active
Sessionobjects. - Implement a
ScheduledExecutorService(or a separate background thread) that generates a random "price" for a stock (e.g., "AAPL: 150.25") every 2 seconds. - The background task must iterate through all connected sessions and push the current price update to every open session.
- Ensure that when a user disconnects, they are removed from the broadcast list to prevent memory leaks.
There are no comments for now.