Integrating WebSocket in Spring Boot Applications
To enable WebSocket functionality in a Spring Boot project, include the following dependency in your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
Configure WebSocket support by creating a configuration class:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter endpointExporter() {
return new ServerEndpointExporter();
}
}
Implement a WebSocket endpoint to manage client connections and messages. This example demonstrates a chat server that broadcasts messages to all connected clients:
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@Component
@ServerEndpoint("/chat/{user}")
public class ChatEndpoint {
private static AtomicInteger activeConnections = new AtomicInteger(0);
private static Map<String, Session> sessionRegistry = new ConcurrentHashMap<>();
@OnOpen
public void handleConnectionOpen(Session session, @PathParam("user") String user) {
activeConnections.incrementAndGet();
sessionRegistry.put(session.getId(), session);
System.out.println(LocalDateTime.now() + ": Connection established with user " + user + ", ID: " + session.getId() + ", Total connections: " + activeConnections);
}
@OnClose
public void handleConnectionClose(Session session, @PathParam("user") String user) {
activeConnections.decrementAndGet();
sessionRegistry.remove(session.getId());
System.out.println(LocalDateTime.now() + ": Connection closed for user " + user + ", ID: " + session.getId() + ", Total connections: " + activeConnections);
}
@OnError
public void handleConnectionError(Throwable error, Session session, @PathParam("user") String user) {
error.printStackTrace();
}
@OnMessage
public void handleIncomingMessage(Session session, String message, @PathParam("user") String user) throws IOException {
System.out.println(LocalDateTime.now() + ": Message from session " + session.getId() + ": " + message);
broadcastMessage(message);
}
private void broadcastMessage(String content) {
sessionRegistry.values().forEach(session -> {
session.getAsyncRemote().sendText(content);
});
}
}
For scenarios reuqiring dependancy injection in WebSocket endpoints, implement ApplicationContextAware too access Spring beans:
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.Instant;
@Component
@ServerEndpoint("/echo")
public class EchoEndpoint implements ApplicationContextAware {
private Session currentSession;
private static ApplicationContext springContext;
private MessageRepository messageRepo;
@OnMessage
public void processMessage(String text) throws IOException {
Message msg = new Message();
msg.setReceiverId(1L);
msg.setSenderId(2L);
msg.setText(text);
messageRepo.save(msg);
System.out.println("[WebSocket] Received message: " + text + " from session " + currentSession.getId());
if (text.equalsIgnoreCase("bye")) {
currentSession.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Goodbye"));
return;
}
currentSession.getAsyncRemote().sendText("[" + Instant.now().toEpochMilli() + "] Echo: " + text);
}
@OnOpen
public void initializeConnection(Session session, EndpointConfig config) {
this.currentSession = session;
this.messageRepo = springContext.getBean(MessageRepository.class);
System.out.println("[WebSocket] New connection: " + session.getId());
}
@OnClose
public void terminateConnection(CloseReason reason) {
System.out.println("[WebSocket] Connection closed: " + currentSession.getId() + ", reason: " + reason);
}
@OnError
public void handleError(Throwable exception) throws IOException {
System.out.println("[WebSocket] Error in session " + currentSession.getId() + ": " + exception.getMessage());
currentSession.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, exception.getMessage()));
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
EchoEndpoint.springContext = context;
}
}
A Vue.js client can connect to the WebSocket server and send/receive messages. This example uses a simple chat interface:
<template>
<div>
<table>
<thead>
<tr>
<th>Message ID</th>
<th>Sender</th>
<th>Time</th>
<th>Content</th>
</tr>
</thead>
<tbody>
<tr v-for="msg in messages" :key="msg.id">
<td>{{ msg.id }}</td>
<td>{{ msg.sender }}</td>
<td>{{ new Date(msg.timestamp).toLocaleTimeString() }}</td>
<td>{{ msg.content }}</td>
</tr>
</tbody>
</table>
<input type="text" v-model="inputText" placeholder="Enter message" />
<button @click="sendMessage">Send</button>
<button @click="disconnect">Disconnect</button>
</div>
</template>
<script>
export default {
data() {
return {
socket: null,
user: '',
messages: [],
inputText: ''
};
},
created() {
this.user = localStorage.getItem('username');
if (!this.user) {
this.$router.push({ name: 'Login' });
}
this.initializeSocket();
},
beforeDestroy() {
if (this.socket) {
this.socket.close();
}
},
methods: {
initializeSocket() {
const url = `ws://localhost:8080/chat/${this.user}`;
this.socket = new WebSocket(url);
this.socket.onopen = (event) => console.log('Connected:', event);
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
this.messages.push(data);
};
this.socket.onerror = (event) => console.error('Error:', event);
this.socket.onclose = (event) => console.log('Disconnected:', event);
},
sendMessage() {
const payload = {
id: Date.now(),
content: this.inputText,
sender: this.user,
timestamp: new Date().getTime()
};
this.socket.send(JSON.stringify(payload));
this.inputText = '';
},
disconnect() {
localStorage.removeItem('username');
this.socket.close();
this.$router.push({ name: 'Login' });
}
}
};
</script>