Network Programming in Kotlin: From Theory to Practice
The modern world is a world of interconnected applications. Whether it's a chatbot, an online game, or a microservice architecture — network interaction is at the core of everything. And if earlier network programming was associated exclusively with Java or C++, today Kotlin is confidently entering the stage.
Why Kotlin? This language is not just a "modern Java". It has absorbed the best from the JVM world, adding conciseness, safety, and powerful asynchronous support through coroutines. In this article, we will explore how to write network programming in Kotlin — from classic sockets to elegant solutions with coroutines. You will learn how to create your own TCP server, send a UDP packet, and not go crazy with multithreading.
Why Kotlin is Suitable for Network Tasks?
Before diving into the code, let's understand why Kotlin is an excellent choice for network programming:
- Full Java compatibility: you can use the entire arsenal of
java.net,java.nio, and popular frameworks (Netty, Ktor). - Coroutines: asynchronous code looks like synchronous code. No callback hell.
- Null safety: fewer chances of getting a
NullPointerExceptionwhen working with data streams. - Functional extensions: convenient extension functions for working with bytes and strings.
We will start with basic sockets (like in good old Java), and then move on to a more modern approach with coroutines.
Basics: TCP Server and Client in Kotlin
Let's start with the classics — TCP connections. In Kotlin, we use the same ServerSocket and Socket classes, but with cleaner syntax.
Writing a Simple TCP Echo Server
Our server will receive messages from the client and send them back (echo).
import java.io.BufferedReaderimport java.io.InputStreamReaderimport java.io.PrintWriterimport java.net.ServerSocket
fun main() { val port = 8080 val serverSocket = ServerSocket(port) println("Server started on port $port")
while (true) { // Block while waiting for a client val clientSocket = serverSocket.accept() println("Client connected: ${clientSocket.inetAddress.hostAddress}")
// Handle the client in a separate thread (for educational purposes) Thread { try { val input = BufferedReader(InputStreamReader(clientSocket.inputStream)) val output = PrintWriter(clientSocket.outputStream, true)
var message: String? while (input.readLine().also { message = it } != null) { println("Received: $message") output.println("Echo: $message") } } catch (e: Exception) { println("Error: ${e.message}") } finally { clientSocket.close() } }.start() }}Note: we use Thread { ... }.start(). This works, but for production it's better to use a thread pool or coroutines (more on that later).
Client for the Echo Server
Now let's write a client that connects to the server and sends messages:
import java.io.BufferedReaderimport java.io.InputStreamReaderimport java.io.PrintWriterimport java.net.Socket
fun main() { val host = "localhost" val port = 8080
try { val socket = Socket(host, port) val output = PrintWriter(socket.outputStream, true) val input = BufferedReader(InputStreamReader(socket.inputStream))
// Send messages val messages = listOf("Hello, server!", "How are you?", "Goodbye!")