Skip to content

Keep UIs in sync via WebSockets

Push the current task to a browser whenever that task changes.

Use the Task, TaskId and commands from Core concepts. The browser opens one socket for one task. It receives an initial value, then a fresh value after changes.

@SocketEndpoint
@Path("/tasks/{taskId}/live")
public record TaskSocket(SocketSession session, TaskId taskId) {
@HandleSocketOpen
static TaskSocket open(SocketSession session, @PathParam TaskId taskId) {
var socket = new TaskSocket(session, taskId);
socket.refresh();
return socket;
}
@HandleNotification
void changed(Graph<Task> task) {
if (taskId.getId().equals(task.functionalId())) refresh();
}
void refresh() {
session.sendMessage(Fluxzero.loadModel(taskId).get());
}
}

Register TaskSocket.class (TaskSocket::class.java in Kotlin), or let Spring discover the endpoint. Each connection gets its own instance. A Model notification asks it to read and send the latest state.

Serve this browser code from your application’s public origin:

const url = new URL('/tasks/t1/live', location.href);
url.protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const socket = new WebSocket(url);
socket.onmessage = event => {
const task = JSON.parse(event.data);
document.querySelector('#task').textContent =
task === null ? 'Task removed' : task.completed ? 'Done' : task.name;
};

Create task t1 first and include an element with id="task" in the page. Send CompleteTask from another client: the displayed value changes to “Done”. Reconnecting opens a new socket and reads the current value again.

This example is a public task view. For private data, check access on connection and before each update; see access control. Notifications prompt a refresh; they are not a durable event history for the browser.

See WebSocket handlers for connection lifecycle and HandleNotification for notification handling.


© 2026 Fluxzero