DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • How To Build Self-Hosted RSS Feed Reader Using Spring Boot and Redis
  • How to Use Java to Build Single Sign-on
  • Full-Duplex Scalable Client-Server Communication with WebSockets and Spring Boot (Part I)
  • A Practical Guide to Creating a Spring Modulith Project

Trending

  • Metrics at a Glance for Production Clusters
  • Exploring Intercooler.js: Simplify AJAX With HTML Attributes
  • Operational Principles, Architecture, Benefits, and Limitations of Artificial Intelligence Large Language Models
  • How to Perform Custom Error Handling With ANTLR
  1. DZone
  2. Coding
  3. Frameworks
  4. Build a Chat Application Using Spring Boot + WebSocket + RabbitMQ

Build a Chat Application Using Spring Boot + WebSocket + RabbitMQ

Learn how to create a web-based chat app by using several interesting technologies across a full-stack application.

By 
Vijay Maniyar user avatar
Vijay Maniyar
·
Jan. 31, 19 · Tutorial
Likes (10)
Comment
Save
Tweet
Share
104.4K Views

Join the DZone community and get the full member experience.

Join For Free

In a previous post, we had created a Spring Boot + WebSocket Hello World Example. In this post, we will be creating a real-time multi-use chat application.

In a previous post, we had also seen how to deploy Spring Boot + RabbitMQ applications to Pivotal Cloud Foundry. I have hosted the real-time chat application that we are creating to Pivotal Cloud Foundry and use can see the demo at JavaInUse Chat Application.

JavaInUse Chat Application Demo 

Spring Boot Websocket Chat Application Architecture

For this tutorial, we will be making use of the STOMP protocol. STOMP is a simple text-oriented messaging protocol used by our UI Client (browser) to connect to enterprise message brokers.

Clients can use the SEND or SUBSCRIBE commands to send or subscribe for messages along with a "destination" header that describes what the message is about and who should receive it.

It defines a protocol for clients and servers to communicate with messaging semantics. It does not define any implementation details, but rather addresses an easy-to-implement wire protocol for messaging integrations. The protocol is broadly similar to HTTP, and works over TCP using the following commands:

  • CONNECT
  • SEND
  • SUBSCRIBE
  • UNSUBSCRIBE
  • BEGIN
  • COMMIT
  • ABORT
  • ACK
  • NACK
  • DISCONNECT

Spring Boot Websocket Chat STOMP Architecture

When using Spring's STOMP support, the Spring WebSocket application acts as the STOMP broker to clients. Messages are routed to @Controller message-handling methods or to a simple, in-memory broker that keeps track of subscriptions and broadcasts messages to subscribed users.

You can also configure Spring to work with a dedicated STOMP broker (e.g. RabbitMQ, ActiveMQ, etc.) for the actual broadcasting of messages. In that case, Spring maintains TCP connections to the broker, relays messages to it, and also passes messages from it down to connected WebSocket clients.

Let's begin.

Creating the Spring Boot WebSocket Application

The project will be structured as follows:

Spring Boot Websocket Chat Maven Project

Define the pom.xml as follows. Add the spring-boot-starter-websocket and spring-boot-starter-amqpdependency.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>spring-boot-websocket-chat</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>spring-boot-websocket-chat</name>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-reactor-netty</artifactId>
</dependency>
</dependencies>
</project>

Define the domain class WebSocketChatMessage as follows-

package com.javainuse.domain;

public class WebSocketChatMessage {
private String type;
private String content;
private String sender;

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public String getSender() {
return sender;
}

public void setSender(String sender) {
this.sender = sender;
}
}

Define the WebSocket Configuration class.

@Configuration tells us that it is a Spring configuration class. @EnableWebSocketMessageBroker enables WebSocket message handling, backed by a message broker. Here we are using STOMP as a message broker. The method configureMessageBroker() enables a RabbitMQ message broker to carry the messages back to the client on destinations prefixed with "/topic" and "/queue".

Also, here we have configured that all messages with "/app" prefix will be routed to @MessageMapping-annotated methods in the controller class.

For example "/app/chat.sendMessage" is the endpoint that the WebSocketController.sendMessage() method is mapped to handle. 


Spring Boot Websocker Flow


package com.javainuse.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionConnectedEvent;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;

import com.javainuse.domain.WebSocketChatMessage;

@Component
public class WebSocketChatEventListener {

    @Autowired
    private SimpMessageSendingOperations messagingTemplate;

    @EventListener
    public void handleWebSocketConnectListener(SessionConnectedEvent event) {
        System.out.println("Received a new web socket connection");
    }

    @EventListener
    public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
        StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());

        String username = (String) headerAccessor.getSessionAttributes().get("username");
        if(username != null) {

            WebSocketChatMessage chatMessage = new WebSocketChatMessage();
            chatMessage.setType("Leave");
            chatMessage.setSender(username);

            messagingTemplate.convertAndSend("/topic/public", chatMessage);
        }
    }
}

Define the controller class. Previously we have configured the websocket such that all messages coming from the client with prefix "/app" will be routed to the appropriate message handling methods annotated with @MessageMapping. 

For example, a message with destination /app/chat.newUser will be routed to the newUser() method, and a message with destination /app/chat.sendMessage will be routed to the sendMessage()method.

package com.javainuse.controller;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Controller;

import com.javainuse.domain.WebSocketChatMessage;

@Controller
public class WebSocketChatController {

@MessageMapping("/chat.sendMessage")
@SendTo("/topic/javainuse")
public WebSocketChatMessage sendMessage(@Payload WebSocketChatMessage webSocketChatMessage) {
return webSocketChatMessage;
}

@MessageMapping("/chat.newUser")
@SendTo("/topic/javainuse")
public WebSocketChatMessage newUser(@Payload WebSocketChatMessage webSocketChatMessage,
SimpMessageHeaderAccessor headerAccessor) {
headerAccessor.getSessionAttributes().put("username", webSocketChatMessage.getSender());
return webSocketChatMessage;
}
}

Finally, define the Spring Boot Class with the @SpringBootApplication annotation

package com.javainuse;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootChatApplication {

	public static void main(String[] args) {
		
		SpringApplication.run(SpringBootChatApplication.class , args);
	}
}

Define the index.html file. Here we have defined the UI for our chat application. Also, it makes use of the SockJS and stomp libraries. The HTML file contains the user interface for displaying the chat messages. It includes the SockJS and STOMP JavaScript libraries. SockJS is a browser JavaScript library that provides a WebSocket-like object. SockJS gives you a coherent, cross-browser, Javascript API which creates a low latency, full duplex, cross-domain communication channel between the browser and the web server. 

STOMP.js is the stomp client for JavaScript.

<!DOCTYPE html>
<html>
<head>
	<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
	<title>JavaInUse Chat Application | JavaInUse</title>
	<link rel="stylesheet" href="/css/style.css" />
	<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
	<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
</head>
<body>

<div id="welcome-page">
	<div class="welcome-page-container">
		<h1 class="title">Welcome - To join the chat group enter your name</h1>
		<form id="welcomeForm" name="welcomeForm">
			<div class="form-group">
				<input type="text" id="name" placeholder="name" class="form-control" />
			</div>
			<div class="form-group">
				<button type="submit" onclass="accent username-submit">Let's Begin</button>
			</div>
		</form>
	</div>
</div>

<div id="dialogue-page" class="hidden">
	<div class="dialogue-container">
		<div class="dialogue-header">
			<h2>JavaInUse Chat Application</h2>
		</div>
		<ul id="messageList">

		</ul>
		<form id="dialogueForm" name="dialogueForm" nameForm="dialogueForm">
		<div class="form-group">
			<div class="input-group clearfix">
				<input type="text" id="chatMessage" placeholder="Enter a message...." autocomplete="off" class="form-control" />
				<button type="submit" class="glyphicon glyphicon-share-alt">Send</button>
			</div>
		</div>
	</form>
	</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
<script src="/js/script.js"></script>
</body>
</html>

Define the javascript file. The stompClient.subscribe() function takes a callback method which is called whenever a message arrives on the subscribed topic. The connect() function makes use of the SockJS and STOMP client to establish a connection to the to the /websocketApp endpoint that we configured in Spring Boot application. The client subscribes to the /topic/javainuse destination.

'use strict';

document.querySelector('#welcomeForm').addEventListener('submit', connect, true)
document.querySelector('#dialogueForm').addEventListener('submit', sendMessage, true)

var stompClient = null;
var name = null;

function connect(event) {
	name = document.querySelector('#name').value.trim();

	if (name) {
		document.querySelector('#welcome-page').classList.add('hidden');
		document.querySelector('#dialogue-page').classList.remove('hidden');

		var socket = new SockJS('/websocketApp');
		stompClient = Stomp.over(socket);

		stompClient.connect({}, connectionSuccess);
	}
	event.preventDefault();
}

function connectionSuccess() {
	stompClient.subscribe('/topic/javainuse', onMessageReceived);

	stompClient.send("/app/chat.newUser", {}, JSON.stringify({
		sender : name,
		type : 'newUser'
	}))	
}

function sendMessage(event) {
	var messageContent = document.querySelector('#chatMessage').value.trim();

	if (messageContent && stompClient) {
		var chatMessage = {
			sender : name,
			content : document.querySelector('#chatMessage').value,
			type : 'CHAT'
	};

	stompClient.send("/app/chat.sendMessage", {}, JSON
	.stringify(chatMessage));
	document.querySelector('#chatMessage').value = '';
	}
event.preventDefault();
}

function onMessageReceived(payload) {
	var message = JSON.parse(payload.body);

	var messageElement = document.createElement('li');

	if (message.type === 'newUser') {
		messageElement.classList.add('event-data');
		message.content = message.sender + 'has joined the chat';
	} else if (message.type === 'Leave') {
		messageElement.classList.add('event-data');
		message.content = message.sender + 'has left the chat';
	} else {
		messageElement.classList.add('message-data');

	var element = document.createElement('i');
	var text = document.createTextNode(message.sender[0]);
	element.appendChild(text);

	messageElement.appendChild(element);

	var usernameElement = document.createElement('span');
	var usernameText = document.createTextNode(message.sender);
	usernameElement.appendChild(usernameText);
	messageElement.appendChild(usernameElement);
	}

	var textElement = document.createElement('p');
	var messageText = document.createTextNode(message.content);
	textElement.appendChild(messageText);

	messageElement.appendChild(textElement);

	document.querySelector('#messageList').appendChild(messageElement);
	document.querySelector('#messageList').scrollTop = document
	.querySelector('#messageList').scrollHeight;
}

Define the CSS:

{
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}

html, body {
	height: 100%;
	overflow: hidden;
}

body {
	margin: 0;
	padding: 0;
	font-weight: 400;
	font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
	font-size: 1rem;
	line-height: 1.58;
	color: #333;
	background-color: #f4f4f4;
	height: 100%;
}

.clearfix:after {
	display: block;
	content: "";
	clear: both;
}

.hidden {
	display: none;
}

input {
	padding-left: 10px;
	outline: none;
}

h1, h2, h3, h4, h5, h6 {
	margin-top: 20px;
	margin-bottom: 20px;
}

h1 {
	font-size: 1.7em;
}

a {
	color: #128ff2;
}

button {
	box-shadow: none;
	border: 1px solid transparent;
	font-size: 14px;
	outline: none;
	line-height: 100%;
	white-space: nowrap;
	vertical-align: middle;
	padding: 0.6rem 1rem;
	border-radius: 2px;
	transition: all 0.2s ease-in-out;
	cursor: pointer;
	min-height: 38px;
}

button.default {
	background-color: #e8e8e8;
	color: #333;
	box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12);
}

button.primary {
	background-color: #128ff2;
	box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12);
	color: #fff;
}


button.accent {
	background-color: #ff4743;
	box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12);
	color: #fff;
}

#welcome-page {
	text-align: center;
}

.welcome-page-container {
	background-color: grey;
	width: 100%;
	max-width: 500px;
	display: inline-block;
	margin-top: 42px;
	vertical-align: middle;
	position: relative;
	padding: 35px 55px 35px;
	min-height: 250px;
	position: absolute;
	top: 50%;
	left: 0;
	right: 0;
	margin: 0 auto;
	margin-top: -160px;
}

#dialogue-page {
	position: relative;
	height: 100%;
}

.dialogue-container {
	background-color: green;
	margin: 10px 0;
	max-width: 700px;
	margin-left: auto;
	margin-right: auto;
	box-shadow: 0 1px 11px rgba(0, 0, 0, 0.27);
	margin-top: 30px;
	height: calc(100% - 60px);
	max-height: 600px;
	position: relative;
}

#dialogue-page ul {
	list-style-type: none;
	background-color: #FFF;
	margin: 0;
	overflow: auto;
	overflow-y: scroll;
	padding: 0 20px 0px 20px;
	height: calc(100% - 150px);
}

#dialogue-page #dialogueForm {
	padding: 20px;
}

#dialogue-page ul li {
	line-height: 1.5rem;
	padding: 10px 20px;
	margin: 0;
	border-bottom: 1px solid #f4f4f4;
}

#dialogue-page ul li p {
	margin: 0;
}

#dialogue-page .event-data {
	width: 100%;
	text-align: center;
	clear: both;
}

#dialogue-page .event-data p {
	color: #777;
	font-size: 14px;
	word-wrap: break-word;
}

#dialogue-page .message-data {
padding-left: 68px;
position: relative;
}

#dialogue-page .message-data i {
	position: absolute;
	width: 42px;
	height: 42px;
	overflow: hidden;
	left: 10px;
	display: inline-block;
	vertical-align: middle;
	font-size: 18px;
	line-height: 42px;
	color: #fff;
	text-align: center;
	border-radius: 50%;
	font-style: normal;
	text-transform: uppercase;
}

#dialogue-page .message-data span {
	color: #333;
	font-weight: 600;
}

#dialogue-page .message-data p {
	color: #43464b;
}

#dialogueForm .input-group input {
	border: 0;
	padding: 10px;
	background: whitesmoke;
	float: left;
	width: calc(100% - 85px);
}

#dialogueForm .input-group button {
	float: left;
	width: 80px;
	height: 38px;
	margin-left: 5px;
}

.dialogue-header {
	text-align: center;
	padding: 15px;
	border-bottom: 1px solid #ececec;
}

.dialogue-header h2 {
	margin: 0;
	font-weight: 500;
}

@media screen and (max-width: 730px) {
	.dialogue-container {
	margin-left: 10px;
	margin-right: 10px;
	margin-top: 10px;
	}
}

We are done with the required Java code. Now, let's start RabbitMQ. As we had explained in detail in the Getting started with RabbitMQ and perform the steps to start the RabbitMQ.

We will need to perform one additional step with RabbitMQ: install the STOMP plugin for RabbitMQ so that it can work with STOMP Messages 

RabbitMQ STOMP Plugin

Next, start the Spring Boot Chat application by running it as a Java Application. Navigate to the following URL: http://localhost:8080.

Spring Boot Websocker Group

Enter the username.

We are then shown the chat window. 

Spring Boot Chat Join If we go to the RabbitMQ console, we can see it has created a queue. 

Spring Boot Stomp Queue RabbitMQ

Spring Framework Spring Boot application WebSocket Build (game engine)

Opinions expressed by DZone contributors are their own.

Related

  • How To Build Self-Hosted RSS Feed Reader Using Spring Boot and Redis
  • How to Use Java to Build Single Sign-on
  • Full-Duplex Scalable Client-Server Communication with WebSockets and Spring Boot (Part I)
  • A Practical Guide to Creating a Spring Modulith Project

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!