Backend Development 18 min read

Implementing Web Message Push: Short Polling, Long Polling, Iframe Stream, SSE, MQTT and WebSocket

This article explains various web message‑push techniques—including short and long polling, iframe streaming, Server‑Sent Events, MQTT and WebSocket—detailing their principles, advantages, drawbacks, and providing complete Spring Boot and JavaScript code examples for real‑time notification badges.

Wukong Talks Architecture
Wukong Talks Architecture
Wukong Talks Architecture
Implementing Web Message Push: Short Polling, Long Polling, Iframe Stream, SSE, MQTT and WebSocket

Hello everyone, I'm Wukong. This article demonstrates how to implement an in‑site message push feature (the red dot notification) commonly used in web applications.

What is Message Push (push)

Push refers to the proactive delivery of messages from a server to a user's web page or mobile app, such as when a user follows a public account and receives a notification to encourage them to open the app.

Message push is generally divided into web push and mobile push . The former includes site‑internal messages, unread email counts, monitoring alerts, etc.

Short Polling

Short polling is the simplest push method: the browser periodically sends an HTTP request to the server to fetch unread message counts.

setInterval(() => {
  // request method
  messageCount().then((res) => {
    if (res.code === 200) {
      this.messageCount = res.data;
    }
  })
}, 1000);

While easy to implement, short polling generates unnecessary traffic because the client requests even when there are no new messages.

Long Polling

Long polling improves on short polling by keeping the request open until new data is available, reducing server load while maintaining near‑real‑time updates. It is widely used in configuration centers like Nacos and Apollo, and message queues such as Kafka and RocketMQ.

The implementation uses Spring’s DeferredResult (Servlet 3.0) to release the servlet thread and handle the request asynchronously.

@Controller
@RequestMapping("/polling")
public class PollingController {
    // Store long‑polling requests per ID (thread‑safe)
    public static Multimap
> watchRequests =
        Multimaps.synchronizedMultimap(HashMultimap.create());

    @GetMapping(path = "watch/{id}")
    @ResponseBody
    public DeferredResult
watch(@PathVariable String id) {
        DeferredResult
deferredResult = new DeferredResult<>(TIME_OUT);
        deferredResult.onCompletion(() -> watchRequests.remove(id, deferredResult));
        watchRequests.put(id, deferredResult);
        return deferredResult;
    }

    @GetMapping(path = "publish/{id}")
    @ResponseBody
    public String publish(@PathVariable String id) {
        if (watchRequests.containsKey(id)) {
            Collection
> results = watchRequests.get(id);
            for (DeferredResult
dr : results) {
                dr.setResult("I updated " + new Date());
            }
        }
        return "success";
    }
}

If the request exceeds the timeout, an AsyncRequestTimeoutException is thrown and handled globally with @ControllerAdvice to return a 304 status, prompting the client to re‑initiate the long poll.

@ControllerAdvice
public class AsyncRequestTimeoutHandler {
    @ResponseStatus(HttpStatus.NOT_MODIFIED)
    @ResponseBody
    @ExceptionHandler(AsyncRequestTimeoutException.class)
    public String asyncRequestTimeoutHandler(AsyncRequestTimeoutException e) {
        System.out.println("Async request timed out");
        return "304";
    }
}

Iframe Stream

An iframe stream inserts a hidden <iframe> whose src points to an API that continuously writes HTML/JavaScript to the response, creating a long‑living connection.

<iframe src="/iframe/message" style="display:none"></iframe>

The server repeatedly writes a script that updates page elements; however, this approach shows a constantly loading icon in the browser, which is visually undesirable.

Server‑Sent Events (SSE)

SSE uses a single‑direction HTTP stream ( text/event-stream ) to push text data from server to client. It works over standard HTTP, requires no special protocol, and automatically reconnects.

let source = null;
let userId = 7777;
if (window.EventSource) {
    source = new EventSource('http://localhost:7777/sse/sub/' + userId);
    source.addEventListener('open', function(e) { setMessageInnerHTML('Connection opened...'); }, false);
    source.addEventListener('message', function(e) { setMessageInnerHTML(e.data); });
} else {
    setMessageInnerHTML('Your browser does not support SSE');
}

On the server side, a SseEmitter is created, stored in a map, and used to send messages to specific users.

private static Map
sseEmitterMap = new ConcurrentHashMap<>();
public static SseEmitter connect(String userId) {
    try {
        SseEmitter emitter = new SseEmitter(0L); // never timeout
        emitter.onCompletion(completionCallBack(userId));
        emitter.onError(errorCallBack(userId));
        emitter.onTimeout(timeoutCallBack(userId));
        sseEmitterMap.put(userId, emitter);
        count.getAndIncrement();
        return emitter;
    } catch (Exception e) {
        log.info("Failed to create SSE connection for user {}", userId);
    }
    return null;
}
public static void sendMessage(String userId, String message) {
    if (sseEmitterMap.containsKey(userId)) {
        try {
            sseEmitterMap.get(userId).send(message);
        } catch (IOException e) {
            log.error("Push error for user {}: {}", userId, e.getMessage());
            removeUser(userId);
        }
    }
}

MQTT

MQTT (Message Queue Telemetry Transport) is a lightweight publish/subscribe protocol designed for IoT scenarios where bandwidth is limited and connections are unreliable. It operates over TCP/IP and provides reliable message delivery.

Compared with HTTP, MQTT is asynchronous, supports many‑to‑many communication, and is more suitable for massive device fleets.

WebSocket

WebSocket establishes a full‑duplex TCP connection after an initial HTTP handshake, allowing bidirectional real‑time communication.

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

Server‑side implementation uses @ServerEndpoint to define a WebSocket endpoint; client‑side JavaScript creates a WebSocket object, handles open, message, close, and error events, and can send data back to the server.

@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}")
public class WebSocketServer {
    private Session session;
    private static final CopyOnWriteArraySet
webSockets = new CopyOnWriteArraySet<>();
    private static final Map
sessionPool = new HashMap<>();

    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        webSockets.add(this);
        sessionPool.put(userId, session);
        log.info("New WebSocket connection, total: {}", webSockets.size());
    }

    @OnMessage
    public void onMessage(String message) {
        log.info("Received from client: {}", message);
    }

    public void sendOneMessage(String userId, String message) {
        Session s = sessionPool.get(userId);
        if (s != null && s.isOpen()) {
            try {
                log.info("Sending single message: {}", message);
                s.getAsyncRemote().sendText(message);
            } catch (Exception e) { e.printStackTrace(); }
        }
    }
}
<script>
    var ws = new WebSocket('ws://localhost:7777/webSocket/10086');
    console.log('ws state: ' + ws.readyState);
    ws.onopen = function() {
        console.log('ws opened');
        ws.send('test1');
    };
    ws.onmessage = function(event) {
        console.log('Message from server:', event.data);
        ws.close();
    };
    ws.onclose = function() { console.log('ws closed, state: ' + ws.readyState); };
    ws.onerror = function(err) { console.log(err); };
</script>

Custom Push Solutions

In practice, the choice of push technology depends on business requirements, system architecture, and performance constraints; third‑party services (e.g., GoEasy, Jiguang) can also be used when development resources are limited.

The article provides a GitHub repository with all the sample code for reference.

"Your likes and views are appreciated!"
webmessage-pushSpring BootWebSocketMQTTSSElong-polling
Wukong Talks Architecture
Written by

Wukong Talks Architecture

Explaining distributed systems and architecture through stories. Author of the "JVM Performance Tuning in Practice" column, open-source author of "Spring Cloud in Practice PassJava", and independently developed a PMP practice quiz mini-program.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.