No More Polling: Build Real-Time Java Apps with Quarkus WebSocket Next
A hands-on guide to pushing live data updates with clean, efficient Java code. No threads, no boilerplate, just results.
Modern users expect real-time feedback. Whether it’s dashboards, notifications, or collaborative tools, applications need to push updates as they happen—no more hammering F5 or polling endpoints inefficiently.
This tutorial walks you through building a simple but powerful real-time app using Quarkus WebSocket Next: a live JVM heap monitor. Instead of the typical chat example, we’ll build something closer to production use cases—something useful for operations, observability, or just understanding how WebSocket broadcasting works in Quarkus.
Let’s get started. And you can either follow along or go directly to the full example in my GitHub repository.
Prerequisites
To follow along, you’ll need:
Java 17+
Maven 3.8+
A basic understanding of Java. Some familiarity with Quarkus helps, but isn’t required.
Create a New Quarkus Project
Use the Quarkus CLI, code.quarkus.io, or Maven directly to bootstrap your project. You’ll need two key extensions:
quarkus-websockets-next
– WebSocket support using the new programming model.quarkus-scheduler
– For periodically pushing updates to clients.
Create the project with:
mvn io.quarkus.platform:quarkus-maven-plugin:3.22.1:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=realtime-monitor \
-Dextensions="websockets-next,scheduler"
cd realtime-monitor
Implement the WebSocket Server
We’ll expose a WebSocket endpoint at /monitor/heap
and push heap usage stats every 3 seconds.
Create the class: src/main/java/org/acme/realtime/HeapMonitorSocket.java
package org.acme.realtime;
import io.quarkus.scheduler.Scheduled;
import io.quarkus.websockets.next.OnClose;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.OpenConnections;
import io.quarkus.websockets.next.WebSocket;
import io.quarkus.websockets.next.WebSocketConnection;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.text.DecimalFormat;
@WebSocket(path = "/monitor/heap")
public class HeapMonitorSocket {
private static final Logger LOG = Logger.getLogger(HeapMonitorSocket.class);
private static final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
private static final DecimalFormat df = new DecimalFormat("#.##");
private static final double MEGABYTE = 1024.0 * 1024.0;
@Inject
WebSocketConnection connection;
@Inject
OpenConnections openConnections;
@OnOpen
void onOpen() {
LOG.infof("Client connected: %s", connection.id());
sendHeapUsage();
}
@OnClose
void onClose() {
LOG.infof("Client disconnected: %s", connection.id());
}
@Scheduled(every = "3s", delay = 1)
void sendHeapUsage() {
// Get heap memory usage once
MemoryUsage usage = memoryBean.getHeapMemoryUsage();
double usedMB = usage.getUsed() / MEGABYTE;
String formattedUsage = df.format(usedMB) + " MB";
LOG.infof("Heap usage: %s", formattedUsage);
// Filter and send to only open connections
openConnections.stream()
.filter(WebSocketConnection::isOpen) // Only process open connections
.forEach(c -> {
c.sendTextAndAwait(formattedUsage);
LOG.infof("Heap usage sent to connection: %s", c.id());
});
// Log skipped connections
openConnections.stream()
.filter(c -> !c.isOpen())
.forEach(c -> LOG.warnf("Skipping closed connection: %s", c.id()));
}
}
Key Concepts
@WebSocket
: Defines the endpoint path (/monitor/heap
).sendHeapUsage()
: method to get the HeapMemory usageNote the
openConnections.stream()
which iterates over all the open connections and sends a text to them.
OnOpen
()
: executed when a WebSocket client connects@Scheduled
: RunssendHeapUsage()
every 3 seconds — no need for threads or timers.JMX (
MemoryMXBean
) gives us portable access to JVM internals like heap usage.
Add a Simple Frontend
Create src/main/resources/META-INF/resources/index.html
:
<!DOCTYPE html>
<html>
<head>
<title>Live JVM Heap Monitor</title>
<style>
body { font-family: sans-serif; padding: 20px; background: #f4f4f4; }
h1 { color: #333; }
#heapDisplay {
font-size: 1.5em; font-weight: bold; color: #007bff;
background: #fff; padding: 10px 15px; border-radius: 5px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); display: inline-block;
}
#status { margin-top: 10px; color: #666; }
</style>
</head>
<body>
<h1>Live JVM Heap Monitor</h1>
<p>Used Heap: <span id="heapDisplay">Initializing...</span></p>
<p id="status">Status: Disconnected</p>
<script>
const display = document.getElementById('heapDisplay');
const status = document.getElementById('status');
function connect() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = `${protocol}//${window.location.host}/monitor/heap`;
status.textContent = `Status: Connecting to ${url}...`;
const ws = new WebSocket(url);
ws.onopen = () => {
status.textContent = 'Status: Connected';
display.textContent = 'Waiting...';
};
ws.onmessage = (event) => {
display.textContent = event.data;
};
ws.onclose = (event) => {
status.textContent = `Status: Disconnected (${event.code}). Reconnecting...`;
display.textContent = 'Offline';
setTimeout(connect, 5000);
};
ws.onerror = () => {
status.textContent = 'Status: Error';
display.textContent = 'Error';
};
}
connect();
</script>
</body>
</html>
This basic page connects to your WebSocket endpoint and updates the heap usage every few seconds—no refresh needed.
Run It
Start the app in development mode:
quarkus dev
Then visit:
http://localhost:8080
You should see the heap monitor updating live. Open multiple tabs—they’ll all receive updates in sync, thanks to WebSocket broadcasting.
Why This Matters
This example is intentionally minimal, but it demonstrates real production techniques:
Server Push: No polling. Clients receive data only when it changes.
Broadcasting: Just a few lines of code to send to every connected client.
Efficiency: Persistent connections reduce HTTP overhead.
Developer Experience: WebSocket Next + Quarkus Dev Mode = fast, productive iteration.
This pattern works well for dashboards, IoT telemetry, collaborative editors, and more.
Quarkus WebSockets vs. Quarkus WebSockets Next
This example uses the quarkus-websockets-next extension. This extension is a new implementation of the WebSocket API that is more efficient and easier to use than the original quarkus-websockets extension. The original quarkus-websockets extension is still available and will continue to be supported.
Unlike quarkus-websockets, quarkus-web-socket-next does NOT implement Jakarta WebSocket. Instead, it provides a simplified and more modern API that is easier to use. It is also designed to work efficiently with Quarkus' reactive programming model and the Quarkus' networking layer.
What’s Next
You can build on this with:
JSON messages: Structure your data instead of plain strings.
Path parameters: Dynamically subscribe to specific data types (e.g.
/monitor/cpu
).Targeted messaging: Send data to specific sessions, not just broadcast.
Security: Add authentication using
quarkus-oidc
orquarkus-security
.Client reconnection strategies: Make the frontend more robust in case of network failures.
Summary
With Quarkus WebSocket Next, real-time communication in Java is no longer complex. You can build efficient, reactive interfaces that respond instantly—without the overhead of traditional polling.
No extra infrastructure. No ceremony. Just clean, focused code that gets the job done.
Try it in your next dashboard or alerting feature and see the difference.
Thanks