What You Will Learn

In this tutorial we will create a very simple realtime collaboration tool (like google docs).
We will be using WebSockets for this, as WebSockets provides us with two-way communication over a one connection, meaning we won’t have to make additional HTTP requests to send and receive messages. A WebSocket connection stays open, greatly reducing latency (and complexity).


First we create a Maven project with our dependencies (→ Tutorial).
We will be using Javalin for our web-server and WebSockets, and slf4j for logging:


The Kotlin application

The Kotlin application is pretty straightforward. We need:

  • a data class (Collaboration) containing the document and the collaborators
  • a map to keep track of document-ids and Collaborations
  • websocket handlers for connect/message/close

We can get the entire server done in about 30 lines:

import io.javalin.Javalin
import io.javalin.embeddedserver.jetty.websocket.WsSession
import java.util.concurrent.ConcurrentHashMap

data class Collaboration(var doc: String = "", val sessions: MutableSet<WsSession> = ConcurrentHashMap.newKeySet<WsSession>())

fun main(args: Array<String>) {

    val collaborations = ConcurrentHashMap<String, Collaboration>()

    Javalin.create().apply {
        ws("/docs/:doc-id") { ws ->
            ws.onConnect { session ->
                if (collaborations[session.docId] == null) {
                    collaborations[session.docId] = Collaboration()
            ws.onMessage { session, message ->
                collaborations[session.docId]!!.doc = message
                collaborations[session.docId]!!.sessions.filter { it.isOpen }.forEach {
            ws.onClose { session, _, _ ->


val WsSession.docId: String get() = this.param("doc-id")!! // is always present, or route won't match

Building a JavaScript Client

In order to demonstrate that our application works, we can build a JavaScript client. We’ll keep the HTML very simple, we just need a heading and a text area:

    <h1>Open the URL in another tab to start collaborating</h1>
    <textarea placeholder="Type something ..."></textarea>

The JavaScript part could also be very simple, but we want some slightly advanced features:

  • When you open the page, the app should either connect to an existing document or generate a new document with a random id
  • When a WebSocket connection is closed, it should immediately be reestablished
  • When new text is received, the user caret (“text-cursor”) should remain in the same location (easily the most complicated part of the tutorial).
window.onload = setupWebSocket;
window.onhashchange = setupWebSocket;

if (!window.location.hash) { // document-id not present in url
    const newDocumentId = Date.now().toString(36); // this should be more random
    window.history.pushState(null, null, "#" + newDocumentId);

function setupWebSocket() {
    const textArea = document.querySelector("textarea");
    const ws = new WebSocket(`ws://localhost:7070/docs/${window.location.hash.substr(1)}`);
    textArea.onkeyup = () => ws.send(textArea.value);
    ws.onmessage = msg => { // place the caret in the correct position
        const offset = msg.data.length - textArea.value.length;
        const selection = {start: textArea.selectionStart, end: textArea.selectionEnd};
        const startsSame = msg.data.startsWith(textArea.value.substring(0, selection.end));
        const endsSame = msg.data.endsWith(textArea.value.substring(selection.start));
        textArea.value = msg.data;
        if (startsSame && !endsSame) {
            textArea.setSelectionRange(selection.start, selection.end);
        } else if (!startsSame && endsSame) {
            textArea.setSelectionRange(selection.start + offset, selection.end + offset);
        } else { // this is what google docs does...
            textArea.setSelectionRange(selection.start, selection.end + offset);
    ws.onclose = setupWebSocket; // should reconnect if connection is closed

And that’s it! Now try opening localhost:7070 in a couple of different browser windows (that you can see simultaneously) and collaborate with yourself.


We have a working realtime collaboration app written in less than 100 lines of Kotlin and JavaScript. It’s very basic though, some things to add could include:

  • Show who is currently editing the document
  • Persist the data in a database at periodic intervals
  • Replace the textarea with a rich text editor, such as quill
  • Replace the textarea with a code editor such as ace for collaborative programming
  • Improving the collaborative aspects with operational transformation

The use cases are not limited to text and documents though, you should use WebSockets for any project which requires a lot of interactions with low latency. Have fun!