Building a High-Throughput Trading Dashboard with Java WebSockets
How we stream 2 million market data points per second from a Java backend to a smooth, live browser dashboard
Java · WebSockets · Real-Time Systems · Frontend Performance
In financial trading systems, the backend can receive and process millions of raw market events every second. The real challenge is not just handling that volume on the server — it is getting meaningful, live data to the browser without crashing the UI or flooding the network.
The Problem
If you naively push every single market tick to the browser, the frontend will freeze. The DOM cannot re-render millions of times per second. Even if it could, most of those updates would be thrown away — the screen only refreshes 60 times per second.
You need a smarter architecture. The backend should handle the raw volume, and a batching layer should deliver only what the browser actually needs.
Architecture
The aggregation layer is the key. The Java backend processes every raw tick internally, but only sends one compact snapshot to the browser every 50 ms. That snapshot contains the latest state for all 24 symbols — not 2 million individual messages.
Live Dashboard
Here is the dashboard running live. The Market Pulse chart shows real-time price lines for each symbol, and the Live Prices table updates continuously over WebSocket — no page refresh involved.
The Top Movers view sorts all 24 symbols by absolute change percentage — green for gainers, red for losers — updated every 50 ms from the WebSocket feed.
By the Numbers
The Critical Distinction
⚠️ Common Misunderstanding
"The browser renders 2 million updates per second"
"Java processes 2M raw events/sec on the backend and sends 20 optimized WebSocket snapshots/sec to the browser"
This separation is the whole engineering point. The backend scales to handle any data volume. The frontend stays smooth and stable regardless of what is happening behind the scenes.
WebSocket Messages in DevTools
You can verify the live feed directly in Chrome DevTools. Open Network → Socket → /ws → Messages tab. You will see JSON snapshot payloads arriving approximately every 50 ms.
Each snapshot payload looks like this:
{
"type": "snapshot",
"ts": 1780294404552,
"backendPointsPerSecond": 2002000,
"pushIntervalMs": 50,
"symbols": [
{ "symbol": "AAPL", "price": 618.34, "changePercent": 60.37, "volume": 6556701990 },
{ "symbol": "MSFT", "price": 412.55, "changePercent": 45.21, "volume": 6554821003 },
{ "symbol": "GOOGL", "price": 494.97, "changePercent": 37.53, "volume": 7688612436 }
]
}
How the Java Backend Works
1. Virtual Threads for Scale
Each WebSocket client connection runs on a lightweight Java virtual thread via Executors.newVirtualThreadPerTaskExecutor(). This lets the server handle many concurrent browser clients without the overhead of traditional OS threads.
2. Scheduled Aggregation
Three scheduled tasks run on a thread pool:
- simulateRawMarketFeed — fires every 1 ms, generates raw ticks for all symbols and updates in-memory state
- broadcastAggregatedSnapshot — fires every 50 ms, serializes current symbol state to JSON and pushes to all connected WebSocket clients
- printMetrics — fires every 1 second, logs throughput to the terminal
3. Frontend Buffering
The browser uses requestAnimationFrame for chart rendering. Incoming WebSocket messages are buffered in a JavaScript array. On each animation frame, the latest snapshot is consumed and used to update the chart and price table — preventing unnecessary re-renders when data arrives faster than the screen refresh rate.
Backend Terminal — Live Metrics
The terminal confirms the backend is consistently generating approximately 2 million raw data points per second: