Real-time message push is ubiquitous in modern applications, from real-time sales data display on e-commerce websites, to instant message notifications on social platforms, and real-time interactions in games. Real-time message push technology permeates our digital lives.
Today, let’s dive deep into the secrets of real-time message push and explore which solution, from polling to MQTT, is your best choice.
1. Polling — Short Polling
Short polling is like calling your friend every few minutes to ask, “Any news yet?” Whether they have something or not, you call anyway. The frontend uses a scheduled task (setInterval) to send requests to the backend at regular intervals. If the backend has data, it returns it; otherwise, it returns nothing.
Frontend Code Example
// Execute every 3 seconds
setInterval(function() {
// Send a request to ‘/get-data’
fetch(’/get-data’)
.then(response => response.json()) // Parse the response as JSON
.then(data => {
if (data) {
console.log(’New message received’, data); // If data exists, log it to the console
}
});
}, 3000); // Time interval is 3000 milliseconds (3 seconds)Backend Code Example
@RestController
public class ShortPollingController {
@GetMapping(”/get-data”)
public ResponseEntity<Map<String, Object>> getData() {
Map<String, Object> result = new HashMap<>();
// Simulate data retrieval logic
boolean hasData = new Random().nextBoolean();
if (hasData) {
result.put(”message”, “This is a new message”);
return ResponseEntity.ok(result);
} else {
return ResponseEntity.noContent().build();
}
}
}The issue with short polling is obvious: the frontend frequently sends requests, but only a few or even none may be valid. This wastes server resources, and with high request volumes, the server faces significant pressure.
2. Polling — Long Polling
Long polling is smarter. After the frontend sends a request, the backend doesn’t respond immediately but holds the request until new data is available. If no data arrives within a timeout, the backend responds, and the frontend sends a new request.
Frontend Code Example
function longPolling() {
// Send a request to ‘/long-polling’
fetch(’/long-polling’)
.then(response => response.json()) // Parse the response as JSON
.then(data => {
console.log(’Long polling received message’, data); // Log the received message
// After receiving the message, initiate the next polling
longPolling();
});
}
// Start the longBackend Code Example
@RestController
public class LongPollingController {
private volatile Map<String, Object> data = new HashMap<>();
@GetMapping(”/long-polling”)
public ResponseEntity<Map<String, Object>> longPolling() {
// Simulate waiting for new messages
synchronized (this) {
while (data.isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
ResponseEntity<Map<String, Object>> response = ResponseEntity.ok(data);
data = new HashMap<>(); // Clear the data
return response;
}
// Simulate a method to receive new messages
@PostMapping(”/send-data”)
public ResponseEntity<String> sendData(@RequestBody Map<String, Object> newData) {
synchronized (this) {
data = newData;
this.notifyAll();
}
return ResponseEntity.ok(”Message sent successfully”);
}
}Long polling reduces the number of requests but comes with its own set of issues. If the backend has no new data, the request will time out; under high concurrency, a large number of threads accumulate, putting immense pressure on the server.
3. SSE Data Push Solution
SSE (Server-Sent Events) is a one-way communication protocol based on HTTP. The frontend initiates a request, and the backend establishes a long-lived connection. Once the client receives it, a persistent connection is established, allowing the server to push data to the client in real-time when changes occur. This is ideal for scenarios requiring frequent data updates and low latency, such as online chat, real-time monitoring, and news feeds.
Frontend Code Example
// Create an EventSource connection to ‘/sse-connect’
const eventSource = new EventSource(’/sse-connect’);
// Handle incoming messages
eventSource.onmessage = function(event) {
console.log(’Message received:’, event.data);
};
// Handle connection errors
eventSource.onerror = function(error) {
console.error(’Connection error:’, error);
};
// Handle successful connection
eventSource.onopen = function() {
console.log(’Connection established’);
};Backend Code Example
@RestController
public class SseController {
private final Map<String, SseEmitter> clients = new ConcurrentHashMap<>();
@GetMapping(value = “/sse-connect”, produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter connect() {
SseEmitter sseEmitter = new SseEmitter();
String clientId = UUID.randomUUID().toString();
// Set timeout
sseEmitter.timeout(0L);
// Send initial message
try {
sseEmitter.send(SseEmitter.event()
.data(”Connection successful”)
.comment(”Connection established”));
} catch (IOException e) {
e.printStackTrace();
}
// Completion callback
sseEmitter.onCompletion(() -> {
System.out.println(”Client “ + clientId + “ disconnected”);
clients.remove(clientId);
});
// Timeout callback
sseEmitter.onTimeout(() -> {
System.out.println(”Client “ + clientId + “ connection timed out”);
clients.remove(clientId);
});
// Error callback
sseEmitter.onError(throwable -> {
System.out.println(”Client “ + clientId + “ connection error”);
clients.remove(clientId);
});
clients.put(clientId, sseEmitter);
return sseEmitter;
}
// Method to push messagesSSE is a solid choice, but it has its limitations, such as being unidirectional — data can only be pushed from the server to the client, not the other way around.
4. WebSocket Solution
WebSocket is a game-changer. It establishes a persistent, bidirectional channel, allowing both the client and server to send messages to each other at any time. This makes it ideal for scenarios requiring real-time, two-way communication, such as online chat or multiplayer gaming.
Frontend Code Example
// Create a WebSocket connection to ‘ws://localhost:8080/ws-connect’
const ws = new WebSocket(’ws://localhost:8080/ws-connect’);
// Handle connection open event
ws.onopen = function() {
console.log(’Connection established’);
ws.send(’Client connected’);
};
// Handle incoming messages
ws.onmessage = function(event) {
console.log(’Message received from server:’, event.data);
};
// Handle connection errors
ws.onerror = function(error) {
console.error(’Connection error:’, error);
};
// Handle connection close event
ws.onclose = function() {
console.log(’Connection closed’);
};Backend Code Example
@ServerEndpoint(”/ws-connect”)
@Component
public class WebSocketServer {
private static final ConcurrentHashMap<String, Session> clients = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session, @PathParam(”userId”) String userId) {
System.out.println(”Client “ + userId + “ is establishing a connection”);
clients.put(userId, session);
}
@OnMessage
public void onMessage(String message, @PathParam(”userId”) String userId) {
System.out.println(”Client “ + userId + “ sent a message to the server: “ + message);
// Broadcast the message to all clients
clients.forEach((id, session) -> {
try {
session.getBasicRemote().sendText(”Client “ + userId + “ says: “ + message);
} catch (IOException e) {
e.printStackTrace();
}
});
}
@OnClose
public void onClose(@PathParam(”userId”) String userId) {
System.out.println(”Client “ + userId + “ closed the connection”);
clients.remove(userId);
}
@OnError
public void onError(Session session, Throwable throwable) {
System.out.println(”An error occurred in the client connection”);
throwable.printStackTrace();
}
}WebSocket performs well but has its pitfalls, such as a relatively complex connection establishment process and higher resource consumption on the server.
5. Netty Real-Time Push Solution
Netty is a high-performance network application framework. Push solutions implemented with Netty are well-suited for scenarios demanding high concurrency and real-time performance, such as large-scale game servers and financial trading systems.
Code Example
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new NettyServerHandler());
}
});
ChannelFuture future = bootstrap.bind(8080).sync();
System.out.println(”Netty server started, listening on port 8080”);
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
// Netty server handler class
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
String message = (String) msg;
System.out.println(”Received message from client: “ + message);
// Broadcast the message to all clients
ctx.channel().group().writeAndFlush(”Server received message: “ + message + “\n”);
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}Netty is powerful in performance but has a steep learning curve. To fully leverage its advantages, a deep understanding of its principles and APIs is required.
6. MQTT for Real-Time Message Push
MQTT is a lightweight messaging protocol, ideal for resource-constrained devices and unstable network environments, such as IoT devices.
Code Example
public class MqttExample {
public static void main(String[] args) {
// MQTT broker URL
String brokerUrl = “tcp://localhost:1883”;
// Client ID
String clientId = “JavaClient”;
MqttClient client;
try {
client = new MqttClient(brokerUrl, clientId);
// Connection options
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true);
// Connect to the broker
client.connect(options);
// Subscribe to a topic
client.subscribe(”test/topic”);
// Set callback
client.setCallback(new MqttCallback() {
@Override
public void connectionLost(Throwable cause) {
System.out.println(”Connection lost”);
}
@Override
public void messageArrived(String topic, MqttMessage message) {
System.out.println(”Message received: “ + new String(message.getPayload()));
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
System.out.println(”Message delivery complete”);
}
});
// Publish a message
MqttMessage msg = new MqttMessage(”Hello MQTT”.getBytes());
msg.setQos(1);
client.publish(”test/topic”, msg);
} catch (MqttException e) {
e.printStackTrace();
}
}
}MQTT’s QoS mechanism ensures message reliability but also introduces some additional complexity and latency.

Summary
In summary, real-time message push solutions each have their strengths. For simple use cases with low request volumes, polling can suffice; for complex scenarios with high request loads, WebSocket and Netty are excellent choices; and for IoT applications, MQTT is the go-to option. Choose the right solution, and your application will navigate the world of real-time message push with ease!
Finally, if the article was helpful, please clap 👏and follow, thank you! ╰(*°▽°*)╯
I’m Dylan, looking forward to progressing with you. ❤️


