🚚 Building a Real-Time Freight Tracker in Java (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>
⚙️ 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");
}
}
🔑 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
.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);
}
}
SimpMessagingTemplate is used to send messages to WebSocket subscribers (like RestTemplate is used for HTTP).
🧠 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;
}
We do the same in the PUT /api/shipments/{id} method.
📨 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;
}
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/[email protected]/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>
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
@EnableWebSocketMessageBrokerto configure endpoints - How
SimpMessagingTemplateis 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 🚀