Creating a WebSocket chat
In this tutorial, you will learn how to create a simple chat application that uses WebSockets. The solution consists of two parts:
The chat server application will accept and manage connections from the chat users, receive messages, and distribute them to all connected clients.
The chat client application will allow users to join a common chat server, send messages, and read user messages in the terminal. To follow this tutorial, see Creating a WebSocket chat client.

Because Ktor is both a server-side and a client-side framework, you will reuse the knowledge you acquire from this part of the tutorial onto the client-side implementation.
This tutorial will teach you how to:
Work with WebSockets using Ktor.
Exchange information between a client and a server.
Manage multiple WebSocket connections simultaneously.
Why WebSockets?
WebSockets are exceptionally well-suited for applications like chats or simple games. Chat sessions are usually long-lived, with the client receiving messages from other participants over a long period of time. These chat sessions operate bidirectionally, allowing clients to both send and receive chat messages.
Unlike standard HTTP requests, WebSocket connections can remain open for extended durations, providing a straightforward interface for data exchange between the client and server through frames. You can think of frames as WebSocket messages which come in different types (text, binary, close, ping/pong). Because Ktor provides high-level abstractions over the WebSocket protocol, you can focus on handling text and binary frames, while Ktor takes care of managing other frame types.
Furthermore, WebSockets are a widely supported technology. All modern browsers support WebSockets out of the box, and many programming languages and platforms have existing support.
Prerequisites
Before starting this tutorial:
Make sure the Ktor plugin is installed and enabled.
Create a new Ktor project
To create a base project for the application using the Ktor plugin, open IntelliJ IDEA and follow the steps below:
On the Welcome screen, click New Project.
Otherwise, from the main menu, select
.In the New Project wizard, choose Ktor from the list on the left. On the right pane, specify the following settings:
Name: Specify a project name.
Location: Specify a directory for your project.
Build System: Make sure that Gradle Kotlin is selected as a build system.
Website: Leave the default
example.com
value as a domain used to generate a package name.Artifact: This field shows a generated artifact name.
Ktor version: Choose the latest Ktor version.
Engine: Leave the default Netty engine.
Configuration in: Choose HOCON file to specify server parameters in a dedicated configuration file.
Add sample code: Disable this option to skip adding sample code for plugins.
Click Next.
On the next page, add the Routing and WebSockets plugins:
Click Create and wait until IntelliJ IDEA generates a project and installs the dependencies.
Examine the project
To see the structure of the generated project, invoke the Project view:

The
build.gradle.kts
file contains dependencies required for a Ktor server and plugins.The
main/resources
folder includes configuration files.The
main/kotlin
folder contains the generated source code.
Dependencies
First, open the build.gradle.kts
file and examine the added dependencies:
ktor-server-core-jvm
adds Ktor's core components to the project.ktor-server-websockets-jvm
allows you to use the WebSocket plugin, the main communication mechanism for the chat.ktor-server-netty-jvm
adds the Netty engine to the project, allowing you to use server functionality without having to rely on an external application container.
ktor-server-tests-jvm
andkotlin-test-junit
allow you to test parts of your Ktor application without having to use the whole HTTP stack in the process.
Configurations: application.conf and logback.xml
The generated project also includes the application.conf
and logback.xml
configuration files located in the resources
folder:
application.conf
is a configuration file in HOCON format. Ktor uses this file to determine the port on which it should run, and it also defines the entry point of the application.ktor { deployment { port = 8080 port = ${?PORT} } application { modules = [ com.example.ApplicationKt.module ] } }To learn more about how a Ktor server is configured, see the Configuration in a file help topic.
logback.xml
sets up the basic logging structure for the server. To learn more about logging in Ktor, see the Logging topic.
Source code
The application.conf file configures the entry point of your application to be com.example.ApplicationKt.module
. This corresponds to the Application.module()
function in Application.kt
, which is an application module:
This module, in turn, calls the following extension functions:
configureRouting
is a function defined inplugins/Routing.kt
, which currently doesn't do anything:fun Application.configureRouting() { routing { } }configureSockets
is a function defined inplugins/Sockets.kt
, which installs and configures theWebSockets
plugin:fun Application.configureSockets() { install(WebSockets) { pingPeriod = Duration.ofSeconds(15) timeout = Duration.ofSeconds(15) maxFrameSize = Long.MAX_VALUE masking = false } routing { } }
A first echo server
Implement an echo server
You will begin by building an “echo” service which accepts WebSocket connections, receives text content, and sends it back to the client. To implement this service with Ktor, add the following implementation for Application.configureSockets()
to plugins/Sockets.kt
:
First, Ktor installs the WebSockets
plugin to the server to enable routing to endpoints responding to the WebSocket protocol (in this case, the route is /chat
). Within the scope of the webSocket
route function, Ktor provides access to various methods for interacting with the clients (through the DefaultWebSocketServerSession
receiver type). This includes convenience methods to send messages and iterate over received messages.
When iterating over the incoming channel, the server checks if the received Frame
is of type text and only then reads the text and sends it back to the user with the prefix "You said:"
.
With this, you have built a fully-functioning echo server.
Test the application
To test the application, use a web-based WebSocket client, such as Postman, to connect to the echo service, send a message, and receive the echoed reply.
To start the server, click on the gutter icon next to the main
function in Application.kt
. After the project has finished compiling, you should see a confirmation that the server is running in IntelliJ IDEA's Run tool window:
Using a web-based client, you can now connect to ws://localhost:8080/chat
and make a WebSocket request.

Enter a message in the editor pane and send it to the local server. You will then see sent and received messages in the Messages pane, indicating that the echo-server is functioning as intended.
You now have a solid foundation for establishing bidirectional communication through WebSockets. In the following chapter, you will expand the application to allow multiple participants to send messages to one another.
Exchange messages
To enable message exchange among multiple users, you will ensure that each user's messages are labeled with their respective usernames. Additionally, you will make sure that messages are sent to all other connected users, effectively broadcasting them.
Model connections
Both of these features require to keep track of the connections the server is holding – to know which user is sending the messages, and to know who to broadcast them to.
Ktor handles WebSocket connections using a DefaultWebSocketSession
object, which contains all the required components for WebSocket communication, such as the incoming
and outgoing
channels, convenient communication methods, and more. To simplify the task of assigning usernames, one solution would be to automatically generate usernames for participants based on a counter:
Create a new file in the com.example
package called Connection.kt
and add the following implementation to it:
Note that AtomicInteger
is used as a thread-safe data structure for the counter. This ensures that two users will never receive the same ID for their username – even when their Connection
objects are created simultaneously on separate threads.
Implement connection handling and message propagation
You can now adjust the server to keep track of the Connection
objects, and send messages to all connected clients, prefixed with the correct username. Adjust the implementation of the routing
block in plugins/Sockets.kt
to the following:
The server now stores a (thread-safe) collection of type Connection
. When a user connects, the server creates a Connection
object, which also self-assigns a unique username, and adds it to the collection.
It then sends a message to the user indicating the number of currently connected users. Upon receiving a message from a user, the server appends a unique identifier prefix associated with the user's Connection
object and broadcasts it to all active connections. When the connection is terminated, the client's Connection
object is removed from the collection – either gracefully, when the incoming channel gets closed, or with an Exception
when an unexpected network interruption occurs between the client and server.
To test the new functionality, run the application by clicking on the gutter icon next to the main
function in Application.kt
and use Postman to connect to ws://localhost:8080/chat
. This time, use two or more separate tabs to validate that messages are exchanged properly.


The finished chat server can now receive and send messages from and to multiple participants.
For the full example of the application, see tutorial-websockets-server.
What's next
Congratulations on creating a chat application using Kotlin, Ktor and WebSockets.
In the next tutorial, you will create a chat client for the server, which will allow you to send and receive messages directly from the command line. Because the client will also be implemented with Ktor, you will reuse much of what you just learned about managing WebSockets.
Additionally, you can expand on the server-side functionality. Use the following ideas to improve the application:
Ask users to enter a username on application startup, and persist this name alongside the
Connection
information on the server.Implement a
/whisper
command, to allow users to share a message to a certain person only or a select group of participants.