Backend Development 19 min read

Implement Real‑Time Web Message Push (Red Dot) Using Polling, SSE, WebSocket & MQTT

This article walks through multiple ways to add real‑time in‑site notifications—such as the familiar red‑dot badge—to a web application, covering short and long polling, iframe streaming, Server‑Sent Events, MQTT and WebSocket, with complete Spring Boot code examples and client‑side snippets.

Sanyou's Java Diary
Sanyou's Java Diary
Sanyou's Java Diary
Implement Real‑Time Web Message Push (Red Dot) Using Polling, SSE, WebSocket & MQTT

The author explains how to add an in‑site message push (the red‑dot notification) to a web application, presenting several implementation schemes and providing complete sample code.

Before implementation, the requirements are simple: when an event occurs (e.g., a resource is shared or the backend pushes a message), the web page’s notification badge should increment in real time.

What is push?

Push notifications are messages sent proactively from a server to a user's web page or mobile app to attract attention. They can be classified as web‑side push or mobile‑side push.

Web‑side push is commonly used for site messages, unread email counts, monitoring alerts, etc.

Short polling

Short polling repeatedly sends an HTTP request at a fixed interval (e.g., every second) to fetch the unread count.

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

While simple, short polling generates unnecessary traffic because the client requests even when no new data exists.

Long polling

Long polling keeps the request open until the server has new data, reducing wasted requests. It is widely used in configuration centers (Nacos, Apollo) and message queues (Kafka, RocketMQ).

<code>@Controller
@RequestMapping("/polling")
public class PollingController {
    // store long‑polling requests per ID
    public static Multimap<String, DeferredResult<String>> watchRequests =
        Multimaps.synchronizedMultimap(HashMultimap.create());

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

    @GetMapping("publish/{id}")
    @ResponseBody
    public String publish(@PathVariable String id) {
        if (watchRequests.containsKey(id)) {
            for (DeferredResult<String> dr : watchRequests.get(id)) {
                dr.setResult("Update " + new Date());
            }
        }
        return "success";
    }
}
</code>

If the request times out, an AsyncRequestTimeoutException is thrown and can be handled globally:

<code>@ControllerAdvice
public class AsyncRequestTimeoutHandler {
    @ResponseStatus(HttpStatus.NOT_MODIFIED)
    @ResponseBody
    @ExceptionHandler(AsyncRequestTimeoutException.class)
    public String handle(AsyncRequestTimeoutException e) {
        System.out.println("Async request timeout");
        return "304";
    }
}
</code>

Iframe streaming

By inserting a hidden &lt;iframe&gt; whose src points to a streaming endpoint, the server continuously writes HTML/JS to the iframe, achieving real‑time updates.

<code>&lt;iframe src="/iframe/message" style="display:none"&gt;&lt;/iframe&gt;
</code>

The server writes a script repeatedly to the response:

<code>@Controller
@RequestMapping("/iframe")
public class IframeController {
    @GetMapping("message")
    public void message(HttpServletResponse response) throws IOException, InterruptedException {
        while (true) {
            response.setHeader("Pragma", "no-cache");
            response.setDateHeader("Expires", 0);
            response.setHeader("Cache-Control", "no-cache,no-store");
            response.setStatus(HttpServletResponse.SC_OK);
            response.getWriter().print("<script>parent.document.getElementById('count').innerHTML='" + count.get() + "';</script>");
        }
    }
}
</code>

Because the browser shows a constantly loading iframe, this method is generally discouraged.

Server‑Sent Events (SSE)

SSE uses a single‑direction HTTP connection that streams text/event-stream data from server to client. It is simpler than WebSocket, works over standard HTTP, and supports automatic reconnection.

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

On the server side, a SseEmitter is stored in a map and used to push messages:

<code>private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();
public static SseEmitter connect(String userId) {
    try {
        SseEmitter emitter = new SseEmitter(0L); // never timeout
        emitter.onCompletion(() -> sseEmitterMap.remove(userId));
        emitter.onError(e -> sseEmitterMap.remove(userId));
        emitter.onTimeout(() -> sseEmitterMap.remove(userId));
        sseEmitterMap.put(userId, emitter);
        return emitter;
    } catch (Exception e) { log.info("Create SSE error", e); }
    return null;
}
public static void sendMessage(String userId, String msg) {
    if (sseEmitterMap.containsKey(userId)) {
        try { sseEmitterMap.get(userId).send(msg); }
        catch (IOException e) { log.error("Push error", e); sseEmitterMap.remove(userId); }
    }
}
</code>

SSE is not supported by IE but works well on modern browsers.

MQTT

MQTT (Message Queue Telemetry Transport) is a lightweight publish/subscribe protocol built on TCP/IP, ideal for IoT scenarios where bandwidth is limited and devices need asynchronous messaging.

It separates publishers from subscribers, allowing reliable delivery even on unstable networks, and can broadcast commands to many devices simultaneously.

WebSocket

WebSocket establishes a full‑duplex TCP connection, enabling bidirectional communication after a single handshake.

<code>@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}")
public class WebSocketServer {
    private Session session;
    private static final CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>();
    private static final Map<String, Session> 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 connection, total: " + webSockets.size());
    }

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

    public void sendOneMessage(String userId, String msg) {
        Session s = sessionPool.get(userId);
        if (s != null && s.isOpen()) {
            s.getAsyncRemote().sendText(msg);
        }
    }
}
</code>
<code>&lt;script&gt;
var ws = new WebSocket('ws://localhost:7777/websocket/10086');
ws.onopen = function() { console.log('ws open'); ws.send('test1'); };
ws.onmessage = function(e) { console.log('msg from server:', e.data); ws.close(); };
ws.onerror = function(err) { console.log(err); };
&lt;/script&gt;
</code>

The client opens the connection once and can send/receive messages bidirectionally.

Custom push strategy

In practice, the choice of push technology depends on the specific business scenario. Third‑party services (e.g., GoEasy, Jiguang) are convenient, but large enterprises often build their own platforms that integrate SMS, email, WeChat, and other channels.

Building a robust push system involves content moderation, audience targeting, rate limiting, failure compensation, and handling high‑concurrency data streams.

GitHub repository

All the sample code discussed in the article is available in the author’s GitHub repository. Feel free to star the project if you find it useful.

Spring BootWebSocketweb pushMQTTlong pollingSSE
Sanyou's Java Diary
Written by

Sanyou's Java Diary

Passionate about technology, though not great at solving problems; eager to share, never tire of learning!

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.