Skip to main content
·Brock

Switching the Notification System From Polling to WebSockets

Polling for notifications worked fine at first, but it wasn't going to scale. Here's why I switched to WebSockets and how the migration went.

dev-diaryarchitecturetech-stack

When I first built the notification system on GotNext, I went with the simplest approach: polling. The frontend would hit the /api/notifications endpoint every few seconds to check for new notifications. It worked. It was easy to build. And for a while, it was fine.

Then it wasn't.

Why Polling Worked at First

With a small user base, polling is totally reasonable. Every user's browser sends a request every 5 seconds, the backend checks the database, and returns any new notifications. The load is minimal, the code is simple, and you don't need any special infrastructure.

For a platform like GotNext where notifications matter (match challenges, tournament check-in reminders, dispute updates, team invitations), I needed something from day one. Polling let me ship the feature fast and move on to higher-priority work.

Where It Started Breaking Down

As more users got on the platform, a few problems became obvious:

Unnecessary database load. The vast majority of polling requests returned nothing. No new notifications. But the query still ran every time, for every connected user, every 5 seconds. During the January 17th tournament with 92 players all online at once, the notification polling was generating a noticeable amount of database traffic for zero useful work.

Delayed feel. Five seconds doesn't sound like much, but when you're waiting for a match challenge to come through or a team invitation to appear, that delay feels sluggish. Players would accept a match on one end and the other player wouldn't see the notification for a few seconds. In a competitive context, that lag is frustrating.

Battery and bandwidth on mobile. A lot of GotNext traffic comes from mobile. Constant polling eats battery and data, even when nothing is happening. Not a great experience.

The WebSocket Approach

WebSockets give you a persistent, two-way connection between the client and server. Instead of the client asking "anything new?" every few seconds, the server pushes notifications to the client the instant they happen.

On the backend, I'm using Spring Boot's WebSocket support with STOMP (Simple Text Oriented Messaging Protocol). Each authenticated user subscribes to their own notification channel when they connect. When the server generates a notification (match challenge, tournament update, team invite), it pushes the message directly to that user's channel.

On the frontend, the React client establishes a WebSocket connection on login and listens for incoming messages. When a notification arrives, it updates the notification state immediately. No polling, no delay.

The Migration

I didn't rip out polling all at once. The migration went in phases:

  1. Built the WebSocket infrastructure on the backend. Set up the STOMP broker, authentication handshake, and per-user channels.
  2. Added the WebSocket client to the frontend alongside the existing polling. Both systems ran in parallel for a few days so I could verify the WebSocket notifications were arriving correctly.
  3. Removed polling once I confirmed WebSockets were stable and all notification types were coming through.

Running both in parallel was important. It let me compare the two systems and catch any notifications that the WebSocket path was missing before I killed the polling fallback.

What Changed

The difference was immediately noticeable. Notifications are now instant. When someone challenges you to a match, it shows up the moment they send it. Team invitations appear immediately. Tournament check-in reminders hit right when the window opens.

The database load from notification polling dropped to zero. The server only queries and sends notifications when something actually happens, which is way more efficient than thousands of empty polling requests.

Gotchas

A few things I ran into during the switch:

Connection management. WebSocket connections can drop. Mobile users switching between apps, network changes, browser tabs going to sleep. The client needs reconnection logic with backoff so it doesn't hammer the server when the connection drops.

Authentication. The WebSocket handshake needs to verify the user's JWT token. If the token expires while the connection is open, you need to handle that gracefully rather than just letting the connection silently fail.

Load balancing. If you're running multiple backend instances behind a load balancer, WebSocket connections are sticky. A user connects to one instance, and all their notifications need to route through that instance. This wasn't a problem for me yet (single server), but it's something I'll need to address when I scale out.

Was It Worth It?

Absolutely. The user experience improvement alone justified the work. Notifications feel alive now instead of feeling like a feed that refreshes every few seconds. And removing the polling load gives me headroom as the platform grows.

If you're building a feature that needs real-time updates and you're starting with polling, that's fine. Ship it, get it working, and move on. But plan for the WebSocket migration early, because you'll need it sooner than you think.

    Switching the Notification System From Polling to WebSockets | GotNext.gg