WebSockets offer a method for websites and servers to communicate in real-time. Instead of the website continuously checking with the server for new information, as is the traditional method, WebSockets let the server push new information to the website as soon as it's available. It's like having a live phone call instead of exchanging letters. In this topic, you'll learn to create a basic WebSocket server with Spring Boot.
Installation
Your journey to use WebSockets with Spring starts with adding the required dependencies. You should include the spring-boot-starter-websocket artifact in your build script. Here's how you can add it:
implementation("org.springframework.boot:spring-boot-starter-websocket")Handling connections
A WebSocketHandler controls events related to WebSocket connections. This includes opening and closing connections, and processing incoming messages.
Imagine a scenario where the WebSocket server delivers real-time updates about a simulated stock market. In this case, you'll include features that broadcast stock price updates to all connected clients and process client requests for updating stock data.
To get started, create a StocksWebSocketHandler class that extends TextWebSocketHandler. The Spring Framework offers this class for WebSocket connections, particularly where text-based communication is the main data format. It extends AbstractWebSocketHandler, providing a more efficient way to handle text messages:
Java
@Controller
public class StocksWebSocketHandler extends TextWebSocketHandler {...}Kotlin
@Controller
class StocksWebSocketHandler : TextWebSocketHandler() {...}In this class, you'll need to define two fields. The WebSocketSession objects offer details about individual client connections and stock information. By using thread-safe collections, you can make sure the sessions and stocks collections in your StocksWebSocketHandler class are thread-safe:
Java
@Controller
public class StocksWebSocketHandler extends TextWebSocketHandler {
/* ... */
private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
private final Map<String, Double> stocks = new ConcurrentHashMap<>();
public StocksWebSocketHandler() {
stocks.put("APPLE", 150.0);
stocks.put("GOOGLE", 2800.0);
stocks.put("AMAZON", 3300.0);
}
}Kotlin
@Controller
class StocksWebSocketHandler : TextWebSocketHandler() {
/* ... */
private val sessions = Collections.synchronizedList(CopyOnWriteArrayList<WebSocketSession>())
private val stocks = ConcurrentHashMap(
mapOf(
"APPLE" to 150.0,
"GOOGLE" to 2800.0,
"AMAZON" to 3300.0,
),
)
}A WebSocketSession manages a single WebSocket connection. It handles the lifecycle of a WebSocket connection, including opening, closing, and maintaining the connection state, and even provides methods to send messages to the client.
Clients should receive updates about the stock prices. So, you should implement two methods: one to update prices randomly, and another to broadcast this information to all connected clients:
Java
@Controller
public class StocksWebSocketHandler extends TextWebSocketHandler {
/* ... */
private void updateStockPrices() {
// Randomly update stock prices
Random random = new Random();
stocks.forEach((key, value) -> stocks.put(key, value * (0.95 + random.nextDouble(0.1))));
}
private void broadcastUpdatedPrices() throws IOException {
TextMessage message = new TextMessage(stocks.toString());
for (WebSocketSession session : sessions) {
session.sendMessage(message);
}
}
}Kotlin
@Controller
class StocksWebSocketHandler : TextWebSocketHandler() {
/* ... */
private fun updateStockPrices() {
// Randomly update stock prices
stocks.forEach { (key, value) -> stocks[key] = value * (0.95 + Random.nextDouble(0.1)) }
}
private fun broadcastUpdatedPrices() {
val message = TextMessage(stocks.toString())
sessions.forEach { session -> session.sendMessage(message) }
}
}The TextMessage class symbolizes a text message in a WebSocket connection. It manages text data transfer between the WebSocket server and clients.
Last but not least, you should define callback methods that get triggered when certain events occur, such as when a new client connects, a text message arrives, or the client disconnects:
Java
@Controller
public class StocksWebSocketHandler extends TextWebSocketHandler {
/* ... */
@Override
public void afterConnectionEstablished(WebSocketSession session) throws IOException {
sessions.add(session);
// Send current stock prices to the new client
session.sendMessage(new TextMessage(stocks.toString()));
}
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
if ("UPDATE".equals(message.getPayload())) {
updateStockPrices();
broadcastUpdatedPrices();
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
sessions.remove(session);
}
}Kotlin
@Controller
class StocksWebSocketHandler : TextWebSocketHandler() {
/* ... */
override fun afterConnectionEstablished(session: WebSocketSession) {
sessions.add(session)
// Send current stock prices to the new client
session.sendMessage(TextMessage(stocks.toString()))
}
override fun handleTextMessage(session: WebSocketSession, message: TextMessage) {
if (message.payload == "UPDATE") {
updateStockPrices()
broadcastUpdatedPrices()
}
}
override fun afterConnectionClosed(session: WebSocketSession, status: CloseStatus) {
sessions.remove(session)
}
}Configuration
Now, it's time to set up WebSocket endpoints by implementing the WebSocketConfigurer interface. The interface, provided by Spring, permits you to customize the configuration of WebSocket request processing:
Java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
private final StocksWebSocketHandler handler;
public WebSocketConfig(StocksWebSocketHandler handler) {
this.handler = handler;
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(handler, "/ws");
}
}Kotlin
@Configuration
@EnableWebSocket
class WebSocketConfig(
private val handler: StocksWebSocketHandler
) : WebSocketConfigurer {
override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
registry.addHandler(handler, "/stocks")
}
}Here's what you're doing with WebSocketConfigurer:
You annotate a configuration class with
@EnableWebSocket, which signals Spring to search for WebSocket configuration methods — particularly those defined in an implementation ofWebSocketConfigurer.Utilize the
registerWebSocketHandlersmethod to register WebSocket handlers to specific URLs. This is how you link your custom WebSocket handler (likeWebSocketHandler) with a URL endpoint (for instance,/stocks).A
WebSocketHandlerRegistryserves as storage where you can add multiple WebSocket handlers and associate them with specific URL patterns.
Connecting a client
Having set up the WebSocket server, you can now interact with it as a client. To do this, we'll use Postman. Click on the "New" button to create a new request:
In the pop-up window, select "WebSocket" to establish communication with the WebSocket server:
Configure a new request using the ws:// protocol and the appropriate endpoint:
Click the "Save" button and create a new collection to store your request:
Give your collection a name and click the "Create" button. This allows you to keep your requests organized:
Click the "Connect" button to link to the server. Once connected, you'll see the current stock price:
Now connect a second client and send it an 'UPDATE' message. This will refresh the stock price and broadcast the new price to all connected clients:
If you go back to the first client, you'll find that it also received a message about the stock price update, even though it didn't send a message to the server:
Conclusion
In conclusion, the use of WebSockets emphasizes the game-changing role of real-time web communication. WebSockets mark a major enhancement over traditional HTTP communication by establishing a constant, two-way connection between the client and server, enabling real-time data transfer that doesn't require the client to keep asking for updates.
The key benefit of WebSockets is its ability to allow the server to transmit data to a client as soon as it becomes available. This resembles a continuous phone call compared to the sporadic letter exchange that traditional HTTP polling methods involve.