Mastering Web Push: 6 Real‑World Strategies from Short Polling to SSE and WebSocket

This article walks through six practical web‑push techniques—including short polling, long polling, iframe streaming, Server‑Sent Events, MQTT, and WebSocket—explaining their principles, trade‑offs, and providing complete Spring Boot and JavaScript code samples to help developers implement real‑time notification badges.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Mastering Web Push: 6 Real‑World Strategies from Short Polling to SSE and WebSocket

What is Message Push (push)

Push notifications are proactive messages sent from a server to a web page or mobile app to attract user interaction, such as unread message counts or alerts. Push can be divided into web‑side push and mobile‑side push.

Short Polling

Short polling repeatedly sends HTTP requests at fixed intervals to fetch unread message counts. A simple JavaScript timer can request the count every second.

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 no new data exists.

Long Polling

Long polling keeps the request open until the server has new data, reducing load while preserving near‑real‑time updates. It is widely used in middleware such as Nacos, Apollo, Kafka, and RocketMQ.

In Spring Boot we use DeferredResult (Servlet 3.0) to handle asynchronous requests. A Multimap from Guava stores pending long‑polling requests keyed by an identifier.

@Controller
@RequestMapping("/polling")
public class PollingController {
    public static Multimap<String, DeferredResult<String>> watchRequests =
        Multimaps.synchronizedMultimap(HashMultimap.create());

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

    @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";
    }
}

If the request exceeds the timeout, an AsyncRequestTimeoutException is thrown and handled globally with @ControllerAdvice to return a 304 status, prompting the client to start a new 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 Streaming

By embedding a hidden <iframe> whose src points to a server endpoint that continuously writes HTML/JavaScript, the page receives updates without explicit AJAX calls.

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

The server repeatedly writes a script that updates DOM elements.

@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>");
        }
    }
}

Although simple, the constantly loading iframe can be visually distracting.

Server‑Sent Events (SSE)

SSE uses a single‑direction HTTP stream ( text/event-stream) to push text messages from server to client. It works on standard HTTP servers, supports automatic reconnection, and is easy to implement.

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

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

private static Map<String, SseEmitter> 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);
        return emitter;
    } catch (Exception e) { /* log */ }
    return null;
}
public static void sendMessage(String userId, String message) {
    if (sseEmitterMap.containsKey(userId)) {
        try { sseEmitterMap.get(userId).send(message); }
        catch (IOException e) { /* handle */ }
    }
}

MQTT

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

WebSocket

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

@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 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()) {
            s.getAsyncRemote().sendText(message);
        }
    }
}
var ws = new WebSocket('ws://localhost:7777/webSocket/10086');
ws.onopen = function() { ws.send('test1'); };
ws.onmessage = function(event) { console.log('Server says:', event.data); };
ws.onclose = function() { console.log('WebSocket closed'); };
ws.onerror = function(err) { console.error(err); };

Choosing a Solution

Each method has trade‑offs: short polling is simple but wasteful; long polling reduces load but still generates many requests; iframe streaming is easy but visually noisy; SSE offers simple HTTP‑based one‑way push with auto‑reconnect; MQTT excels in IoT environments; WebSocket provides full‑duplex communication suitable for real‑time interactive apps.

In practice, select the approach that matches your system’s requirements, traffic patterns, and client capabilities.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSpring BootWebSocketweb pushServer-Sent Eventslong polling
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

0 followers
Reader feedback

How this landed with the community

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.