WebSocket Using Spring Boot And Angular

Web Socket

Web Socket is a very thin, lightweight layer above TCP used to build interactive web applications that send messages back and forth between a browser and the server.

The best examples are live updates websites, where once user access the website neither user nor browser sends request to the server to get the latest updates. Server only keeps sending the messages to the browser.

In this example, I am building a WebSocket application with Spring Boot using STOMP Messaging to provide automatic updates on the greeting message depending on the time of the day for every 3 seconds.

Server Application

Prerequisites

Java 12/19, Gradle 5.6, Maven 3.8.5, Spring Boot WebSocket 2.1.8/3.1.5

Project Setup

Create gradle or maven based project called spring-websocket in your favorite IDE.

For maven based project you can refer to the following pom.xml file:

<?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.roytuts</groupId>
	<artifactId>spring-websocket</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>19</maven.compiler.source>
		<maven.compiler.target>19</maven.compiler.target>
	</properties>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.1.5</version>
	</parent>

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

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

The corresponding build.gradle script is updated with the below content to include required dependency and configurations.

buildscript {
	ext {
		springBootVersion = '2.1.8.RELEASE'
	}
	
    repositories {
    	mavenLocal()
    	mavenCentral()
    }
    
    dependencies {
    	classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

sourceCompatibility = 12
targetCompatibility = 12

repositories {
	mavenLocal()
    mavenCentral()
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-websocket:${springBootVersion}")
}

POJO Class

I will create a simple POJO class to represent message that will be sent to the client as a push notification.

package com.roytuts.spring.websocket.vo;

public class Message {

	private String msg;

	public Message(String msg) {
		this.msg = msg;
	}

	public String getMsg() {
		return msg;
	}

}

Web Socket Configuration

Configure Spring to enable Web Socket and STOMP messaging.

package com.roytuts.spring.websocket.config;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		//registry.addEndpoint("/websocket").setAllowedOrigins("*").withSockJS(); // spring boot 2.x.x
		registry.addEndpoint("/websocket").setAllowedOrigins("http://localhost:4200").withSockJS(); //spring boot 3.x.x
	}

	@Override
	public void configureMessageBroker(MessageBrokerRegistry config) {
		config.enableSimpleBroker("/topic");
		config.setApplicationDestinationPrefixes("/app");
	}

}

The above WebSocketConfig class is annotated with @Configuration to indicate that it is a Spring configuration class.

The class is also annotated @EnableWebSocketMessageBroker and @EnableWebSocketMessageBroker enables WebSocket message handling, backed by a message broker.

The configureMessageBroker() method overrides the default method in WebSocketMessageBrokerConfigurer interface to configure the message broker.

It starts by calling enableSimpleBroker() to enable a simple memory-based message broker to carry the greeting messages back to the client on destinations prefixed with /topic.

It also designates the /app prefix for messages that are bound for @MessageMapping annotated methods. This prefix will be used to define all the message mappings; for example, /app/hello is the endpoint that the GreetingRestController.greeting() method is mapped to handle.

The registerStompEndpoints() method registers the /websocket endpoint, enabling SockJS fallback options so that alternate transports may be used if WebSocket is not available.

The SockJS client will attempt to connect to /websocket and use the best transport available (websocket, xhr-streaming, xhr-polling, etc).

Related Posts:

Service Class

This class will generate different greet messages depending upon the time of the day. I have also appended random generated string so that we will get different random string appended with actual greet message to differentiate from each other every 3 seconds and it will actually tell us that we are getting every time the new message.

package com.roytuts.spring.websocket.service;

@Service
public class GreetingService {

	public Message getMessage() {
		Calendar c = Calendar.getInstance();
		int timeOfDay = c.get(Calendar.HOUR_OF_DAY);
		StringBuilder sb = new StringBuilder();
		String message = "Have a Good Day";
		if (timeOfDay >= 0 && timeOfDay < 12) {
			message = "Good Morning";
		} else if (timeOfDay >= 12 && timeOfDay < 16) {
			message = "Good Afternoon";
		} else if (timeOfDay >= 16 && timeOfDay < 21) {
			message = "Good Evening";
		} else if (timeOfDay >= 21 && timeOfDay < 24) {
			message = "Good Night";
		}
		sb.append(message).append(" - ").append(generateString());
		return new Message(sb.toString());
	}

	private String generateString() {
		String uuid = UUID.randomUUID().toString();
		return uuid;
	}

}

REST Controller Class

In Spring’s approach to working with STOMP messaging, STOMP messages can be routed to @Controller or @RestController classes.

For example the GreetingRestController is mapped to handle messages to destination /hello.

package com.roytuts.spring.websocket.rest.controller;

@RestController
public class GreetingRestController {

	@Autowired
	private GreetingService greetingService;

	@MessageMapping("/hello")
	@SendTo("/topic/greeting")
	public Message greeting() {
		return greetingService.getMessage();
	}

}

The @MessageMapping annotation ensures that if a message is sent to destination /hello, then the greeting() method is called.

Scheduler Configuration

Create scheduler to push updates every 3 seconds to client.

package com.roytuts.spring.websocket.config;

@Configuration
@EnableScheduling
public class WebSocketSchedulerConfig {

	@Autowired
	private SimpMessagingTemplate template;

	@Autowired
	private GreetingService greetingService;

	@Scheduled(fixedRate = 3000)
	public void publishUpdates() {
		System.out.println("Message: " + greetingService.getMessage().getMsg());
		template.convertAndSend("/topic/greeting", greetingService.getMessage());
	}

}

Running the Server Application

Run the main class file SpringWebSocketApp and you will see the greeting message is getting displayed in the console.

Console Output

...
Message: Good Evening - 683407fd-22ef-43a5-87f8-85d20b7d4668
Message: Good Evening - 31817e2e-084f-4529-ad2b-c85cc61f8e2b
Message: Good Evening - ba628369-6f3a-4605-a8cf-876acadd578a
Message: Good Evening - f39c0f0d-2bb3-4a74-93d7-82583f561500
...

That’s all about server application. Now I will create the client application.

Client Application

With the server side pieces in place, now let’s move to the client application that will send messages to and receive messages from the server side.

Prerequisites

Angular 8/17, Node 18.17.1, Npm 10.2.3

Creating Angular Project

Create an Angular project called angular-spring-websocket.

Installing Required Modules

Install the required modules with the following commands:

For Angular 17 use the following command format:

npm i --save-dev @types/sockjs-client
npm i --save-dev @types/jquery
npm i --save-dev @types/stompjs
npm i net -S

For Angular 8 use the following command format:

npm install stompjs
npm install sockjs-client
npm install jquery
npm i net -S

The stompjs is required to connect over STOMP.

The sockjs-client is required to establish connection with WebSocket server.

The jquery is required to directly access DOM elements in the HTML page.

To avoid net issue we need to install net module.

Updating index.html

You need to declare window in the src/index.html file to avoid the following issue during runtime of the Angular application:

Uncaught ReferenceError: global is not defined
    at Object../node_modules/sockjs-client/lib/utils/browser-crypto.js

The complete content of src/index.html file is given below:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>AngularSpringWebsocket</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <script>
	if (global === undefined) {
      var global = window;
    }
  </script>
</head>
<body>
  <app-root></app-root>
</body>
</html>

Updating app.component.html

I will update the src/app/app.component.html file to put a div tag where greeting message will be updated.

<div class="msg"></div>

<router-outlet></router-outlet>

Updating app.component.ts

I will update src/app/app.component.ts file to consume the message over STOMP.

I set the page title by implementing OnInit interface in the ngOnInit() method.

I will establish connection to the WebSocket server, client socket subscribe to the topic /topic/greeting destination, where the server will publish greeting messages and finally we update the div (having a class msg) on HTML page.

For Angular 17, use the following code:

import { OnInit, Component } from '@angular/core';
import { Title } from '@angular/platform-browser';

import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';

import Stomp from 'stompjs';
import SockJS from 'sockjs-client';
import $ from 'jquery';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterOutlet],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  url = 'http://localhost:8080/websocket'
  client: any;
  
  ngOnInit() {
	this.title.setTitle('Angular Spring Websocket');
  }
  
  constructor(private title: Title){
    this.connection();
  }
  
  connection(){
    let ws = new SockJS(this.url);
    this.client = Stomp.over(ws);
    let that: any = this;
	
    this.client.connect({}, function(frame: any) {
      that.client.subscribe("/topic/greeting", (message: any) => {
        if(message.body) {
		  $(".msg").html(message.body)
        }
      });
    });
  }
  
}

For Angular 8, use the following code:

import { OnInit, Component } from '@angular/core';
import { Title } from '@angular/platform-browser';

import * as Stomp from 'stompjs';
import * as SockJS from 'sockjs-client';
import $ from 'jquery';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  url = 'http://localhost:8080/websocket'
  client: any;
  greeting: string;
  
  ngOnInit() {
	this.title.setTitle('Angular Spring Websocket');
  }
  
  constructor(private title: Title){
    this.connection();
  }
  
  connection(){
    let ws = new SockJS(this.url);
    this.client = Stomp.over(ws);
    let that = this;
	
    this.client.connect({}, function(frame) {
      that.client.subscribe("/topic/greeting", (message) => {
        if(message.body) {
          this.greeting = message.body;
		  //$(".msg").append(this.greeting)
		  $(".msg").html(this.greeting)
		  //alert(this.greeting);
		  //console.log(this.greeting);
        }
      });
    });
  }
  
}

I do not want to send any message to the destination /app/hello, so I don’t need to call this endpoint. I am here to just get the message from server as a push notification towards clients.

Testing the Client Application

With your server running now run the client application by executing command ng serve --open.

Your application opens at http://localhost:4200 and you will see the message being updated every 3 seconds.

spring boot websocket angular

Source Code

Download

Leave a Reply

Your email address will not be published. Required fields are marked *