Backend Development 19 min read

Implementing Web Message Push: Short Polling, Long Polling, SSE, WebSocket, and MQTT with Spring Boot

This article explains various web message‑push techniques—including short and long polling, iframe streaming, Server‑Sent Events, MQTT, and WebSocket—provides detailed Java and JavaScript code samples, compares their trade‑offs, and shows how to integrate them in a Spring Boot application.

Architect
Architect
Architect
Implementing Web Message Push: Short Polling, Long Polling, SSE, WebSocket, and MQTT with Spring Boot

Message push (push) is a common feature for web applications, such as site‑internal notifications, unread email counts, and monitoring alerts. It can be divided into web‑side and mobile‑side push, with the former covering scenarios like the red‑dot notification shown in the article.

The article first outlines the basic requirement: when an event occurs, the web page should increment the unread count (+1) in real time. Typically, a server‑side table records messages, and the front‑end either pulls (polls) or receives (push) the unread count.

Short Polling

Short polling uses a simple JavaScript timer to send HTTP requests at fixed intervals. The following code demonstrates a basic implementation:

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

While easy, short polling generates unnecessary traffic because the client requests even when no new messages exist.

Long Polling

Long polling improves efficiency by keeping the request open until new data is available or a timeout occurs. The article uses Spring’s DeferredResult (Servlet 3.0) and Guava’s Multimap to manage multiple listeners per ID.

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

A global @ControllerAdvice catches AsyncRequestTimeoutException and returns a 304 status so the front‑end can re‑initiate the request.

Iframe Stream

Iframe streaming inserts a hidden <iframe> whose src points to an API that continuously writes HTML/JavaScript snippets. The server writes a script block with the updated data in a loop. Although simple, the browser shows a constantly loading icon, which is undesirable.

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

Server‑Sent Events (SSE)

SSE establishes a one‑way HTTP connection where the server streams text/event-stream data. It works on most browsers, automatically reconnects, and is easy to implement with Spring’s SseEmitter :

private static Map
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 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 {}: {}", userId, e.getMessage());
            sseEmitterMap.remove(userId);
        }
    }
}

On the client side, an EventSource receives messages:

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

MQTT

MQTT (Message Queue Telemetry Transport) is a lightweight publish/subscribe protocol designed for IoT. It runs over TCP/IP and is more suitable than HTTP for unreliable networks, supporting massive device connections and reliable delivery.

WebSocket

WebSocket provides full‑duplex communication over a single TCP connection. Spring Boot integration requires the spring-boot-starter-websocket dependency and a server endpoint annotated with @ServerEndpoint :

@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()) {
            s.getAsyncRemote().sendText(message);
        }
    }
}

Front‑end JavaScript opens the socket and handles events:

var ws = new WebSocket('ws://localhost:7777/websocket/10086');
ws.onopen = function() { console.log('WebSocket opened'); ws.send('test1'); };
ws.onmessage = function(e) { console.log('Server says:', e.data); ws.close(); };
ws.onerror = function(err) { console.error(err); };
ws.onclose = function() { console.log('WebSocket closed'); };

Custom Push & Third‑Party Services

The article notes that the six presented solutions are building blocks; production systems often combine them with third‑party push platforms (e.g., GoEasy, Jiguang) or internal platforms handling content moderation, audience targeting, rate limiting, and failure compensation.

All example code is available on GitHub at https://github.com/chengxy-nds/Springboot-Notebook/tree/master/springboot-realtime-data .

JavaJavaScriptWebSocketSpringBootweb pushlong pollingSSE
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and 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.