Java Client to Java Server Spring Boot Websocket Communication

Hussein Maziad
5 min readJun 5, 2021

--

Today, we will be coding together a Java to Java Websocket application. The first step is to open up Intellij, create a new maven project (I will call it WebsocketApplication) and create three modules:

  1. Order
  2. Client
  3. Server
Project Directory

Lets start with the easy part, the Order module:

Order Module:

Order.java

We will need an Order class which contains the name of the item and the quantity. This is the order that will be sent from the Client to the Server.

public class Order {
private String itemName;
private int quantity;

public Order() {
}

public Order(String itemName, int quantity) {
this.itemName = itemName;
this.quantity = quantity;
}

public String getItemName() {
return itemName;
}

public int getQuantity() {
return quantity;
}
}

OrderStatus.java

The order lifecyle will be three phases:

  1. ORDER_RECEIVED

2. ORDER_IN_PROGRESS

3. ORDER_SUCCESSFUL

public enum OrderStatus {
ORDER_RECEIVED,
ORDER_IN_PROGRESS,
ORDER_SUCCESSFUL
}

OrderState.java

This is what the server will respond to the client. As you notice, the order state will be composed of the OrderStatus and the username created by the server.

public class OrderState {
private OrderStatus status;
private String user;

private OrderState() {
}

public OrderState(OrderStatus status) {
this.status = status;
}

public OrderState(String user, OrderStatus status) {
this.user = user;
this.status = status;
}

public OrderStatus getStatus() {
return this.status;
}


public String getUser() {
return this.user;
}

@Override
public String toString() {
return "Order Status for user: " + user +" is: " + status;
}
}

Server Module

This module will contain three classes:

  1. MainApplication
  2. WebSocketBrokerConfig
  3. OrderController

MainApplication.java

The MainApplication is simply the SpringBootApplication

@SpringBootApplication
public class MainApplication {

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

}

WebSocketBrokerConfig.java

The WebSocketBrokerConfig will contain the main endpoint that the server will be publishing and listening to.

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketBrokerConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/orders").setHandshakeHandler(new RandomUserNameHandshakeHandler());
}

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/makeorder").enableSimpleBroker("/topic", "/queue");
}

private class RandomUserNameHandshakeHandler extends DefaultHandshakeHandler {

@Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
return new UsernamePasswordAuthenticationToken(UUID.randomUUID(), null);
}
}
}

The server will be publishing to “/orders” endpoint, so this is where every client’s websocket url should be set at, i.e. “ws://localhost:8090/orders”

The RandomUserNameHandshakeHandler will assign a random UUID to each new connection to the server.

The other endpoint is “/makeorder” and this is where the clients will be sending their orders to.

The “.enableSimpleBroker(“/topic”, “/queue”)” means that users connecting to the “/orders” endpoint can subscribe to “/topic” which is a public channel that all users currently connected to the websocket receive or “/queue” which means that each user can receive private responses from the server.

OrderController.java

Here is where the action happens. The MessageMapping annotation will be listening at the “orders” main endpoint. The buildOrder method will receive two arguments

  1. Order (Deserialized automatically by Jackson)
  2. Principle (i.e the UUID created by the server)

Quick Note: When a client first establishes a connection with the websocket and submits a request, the server will first assign that client with the UUID defined in the handshake handler in WebSocketBrokerConfig.java and then the request will be forwarded to the buildOrder method where principal instance contains the UUID.

We use SimpMessageSendingOperations to publish to an endpoint. Notice the format for sending a message to a specific user.

“/user/{username}/queue/private”: In this case the username is the UUID that we assigned and is contained in the Principal instance. I added a synchronized block because I want the server to handle the requests one by one.

@Controller
public class OrderController {

@Autowired
public SimpMessageSendingOperations messagingTemplate;

@MessageMapping("orders")
public void buildOrder(Order order, Principal principal) {
String user = principal.getName();
sendMessage(new OrderState(user, ORDER_RECEIVED));
synchronized (this) {
sleep(2000);
sendMessage(new OrderState(user, ORDER_BEING_BUILT));
sleep(2000);
sendMessage(new OrderState(user, ORDER_SUCCESSFUL));
}
}

public void sendMessage(OrderState state) {
messagingTemplate.convertAndSend(String.format("/user/%s/queue/private", state.getUser()), state);
}

private void sleep(int seconds) {
try {
Thread.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

Client Module

Client.java

This might be the most complicated module but we will walk through it step by step.

In the constructor we pass the Websocket Url (wsUrl) and initilize a BlockingQueue which will allow a timeout before it returns a null. Then we call the setUpWebsocket which will initialize a Stomp client and Session Handler. This session handler will subscribe to an endpoint and receive messages. The sendOrder method will send an order to the endpoint “/makeorder/orders” as specificed by the server. The addOrderState method will be called by the session handler as soon as there is a new order sent by the server. The poll method will wait up to 20 seconds for the session handler to put something inside it.

public class Client {
private final String wsUrl;
private StompSession stompSession;
private BlockingQueue<OrderState> blockingQueue;

public Client(String wsUrl) {
this.wsUrl = wsUrl;
blockingQueue = new ArrayBlockingQueue<>(3);
setUpWebsocket();
}

private void setUpWebsocket() {
OrderSessionHandler handler = new OrderSessionHandler(this);
WebSocketStompClient stompClient = new WebSocketStompClient(new StandardWebSocketClient());
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
try {
stompSession = stompClient.connect(wsUrl, handler).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}

public void sendOrder(Order order) {
stompSession.send("/makeorder/orders", order);
}

public void addOrderState(OrderState state) {
blockingQueue.add(state);
}

public OrderState poll() {
try {
return blockingQueue.poll(20, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
throw new NullPointerException("Request Timed out");
}
}

Main.java

Here the purpose is to create multiple clients and send orders concurrently. And the server is to respond to each user specifically. So we create an order and specify the wsUrl. Then we use an ExecutorService to submit the tasks concurrently. To retrieve the messages, we will poll each message from each client in a for loop.

public class Main {
public static void main(String[] args) throws InterruptedException {
String wsUrl = "ws://localhost:8090/orders";
Order order = new Order("Apple Macbook", 1);
int totalClients = 3;

ExecutorService executorService = Executors.newFixedThreadPool(totalClients);

List<Client> clients = new ArrayList<>();
for (int i = 0; i < totalClients; i++) {
clients.add(new Client(wsUrl));
}

clients.forEach(client -> {
executorService.submit(() -> {
client.sendOrder(order);
for (int j = 0; j < OrderStatus.values().length; j++) {
System.out.println(client.poll());
}
});
});

executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.SECONDS);
}
}

To test this, first run the Server and then run the Client.

Server responses

You can find the dependencies along with the code on my Github repo.

I hope you enjoyed this article :)

--

--