DEV Community

jayanti-neu
jayanti-neu

Posted on • Edited on

🚦 Real-Time Freight Tracker with Java & WebSockets (Phase 2)

After building the core CRUD backend in Phase 1, Phase 2 of the Freight Tracker project adds real-time updates using WebSockets. This allows clients to receive live status changes for shipments without needing to refresh the page.

Its continuation of building a full-stack logistics tracking app, this post adds real-time updates using Spring Boot, STOMP, and WebSockets.


🌐 Why Real-Time with WebSockets?

In logistics applications, users (like clients, warehouse staff, or support teams) often need instant updates on shipment status. Polling the backend repeatedly isn’t efficient — WebSockets provide a two-way communication channel where the server pushes updates to connected clients instantly.

Spring Boot supports this via:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> 
Enter fullscreen mode Exit fullscreen mode

⚙️ Setting up WebSockets in Spring Boot

WebSocketConfig.java

We created a new class called WebSocketConfig.java to enable STOMP messaging and define endpoints:

@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws") .setAllowedOriginPatterns("*") .withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic"); registry.setApplicationDestinationPrefixes("/app"); } } 
Enter fullscreen mode Exit fullscreen mode

Two Sides of WebSocket Communication

When using STOMP over WebSockets in Spring, there are two types of message flow:

Client → Server (incoming messages)

Server → Client (broadcast messages)

We configure these in two parts:

registerStompEndpoints(): Where clients initially connect (like /ws)

configureMessageBroker(): How messages are routed internally (topics, queues, etc.)

🔑 Keywords Explanation:

  • @Configuration: Marks the class as a Spring config class which means it processes the class during startup
  • @EnableWebSocketMessageBroker: Turns on WebSocket messaging with STOMP protocol and enables the broker system (/topic or /queue). It allows you to also map /app/... for client → server messages
  • .addEndpoint("/ws"): Clients connect to this endpoint
  • withSockJS(): Adds fallback support if browser doesn't support native WebSockets
  • enableSimpleBroker("/topic"): Clients subscribe to these topics to receive updates
  • setApplicationDestinationPrefixes("/app"): Prefix for client-to-server messages

📢 Broadcasting Status Updates

To send messages to subscribed clients, we created a broadcaster class:

ShipmentStatusBroadcaster.java

@Component public class ShipmentStatusBroadcaster { @Autowired private SimpMessagingTemplate messagingTemplate; public void broadcastUpdate(ShipmentUpdateMessage message) { messagingTemplate.convertAndSend("/topic/shipments", message); } } 
Enter fullscreen mode Exit fullscreen mode

SimpMessagingTemplate is used to send messages to WebSocket subscribers (like RestTemplate is used for HTTP).

Why Do We Need SimpMessagingTemplate?
The service has business events (e.g., shipment status updated) and we want to push those events to clients manually — not in response to a client message, but whenever the backend decides (like after saving to DB).

SimpMessagingTemplate is a helper that talks directly to the broker we created.

SimpMessagingTemplate.convertAndSend() = Pushes events into the broker

🧠 What is @Component?

This marks the class as a Spring-managed bean. It gets picked up automatically during component scanning and injected wherever needed (like in the ShipmentController).


📦 Updating the Controller

We modified the ShipmentController to send broadcast messages on every shipment creation or update:

@PostMapping public Shipment createShipment(@RequestBody Shipment shipment) { shipment.setLastUpdatedTime(LocalDateTime.now()); Shipment saved = shipmentRepository.save(shipment); broadcaster.broadcastUpdate( ShipmentUpdateMessage.builder() .shipmentId(saved.getId()) .trackingNumber(saved.getTrackingNumber()) .status(saved.getStatus()) .lastUpdatedTime(saved.getLastUpdatedTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)) .build() ); return saved; } 
Enter fullscreen mode Exit fullscreen mode

We do the same in the PUT /api/shipments/{id} method.

Frontend (subscribe) ----connect---> /ws endpoint
|
| Subscribes to /topic/shipments
v
Spring Broker (enabled by enableSimpleBroker)
|
| convertAndSend("/topic/shipments", dto)
v
Frontend receives update in real-time


📨 DTO for Real-Time Updates

ShipmentUpdateMessage.java

@Data @NoArgsConstructor @AllArgsConstructor @Builder public class ShipmentUpdateMessage { private Long shipmentId; private String trackingNumber; private ShipmentStatus status; private String lastUpdatedTime; } 
Enter fullscreen mode Exit fullscreen mode

We keep this DTO separate from the main Shipment model to ensure:

  • Lightweight messages
  • No overexposing of sensitive data
  • Easy control over WebSocket response structure

🔌 Frontend Client Example

Here’s how a basic HTML frontend listens for shipment updates:

<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script> <script> const socket = new SockJS("http://localhost:8080/ws"); const stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { console.log("Connected: " + frame); stompClient.subscribe("/topic/shipments", function (message) { const body = JSON.parse(message.body); console.log(`Shipment #${body.trackingNumber} is now ${body.status} at ${body.lastUpdatedTime}`); }); }); </script> 
Enter fullscreen mode Exit fullscreen mode

Every time a shipment is created or updated, this client will receive a message in real time.


🧠 What I Learned in Phase 2

  • How STOMP protocol simplifies WebSocket communication
  • How to use @EnableWebSocketMessageBroker to configure endpoints
  • How SimpMessagingTemplate is used to push messages
  • When and why to use DTOs for WebSockets
  • How real-time updates improve client experience in logistics

🗓️ Coming Next

  • React-based frontend dashboard
  • Service Layer refactor and error handling
  • Deployment to cloud with Docker and AWS

Thanks for reading — you can find the code here: https://github.com/jayanti-neu/freight-tracker 🚀

Top comments (0)