Why working with files in Kotlin doesn't have to be painful
\n\nImagine: you are writing an application that needs to save user settings, log errors, or process CSV files with thousands of lines. In Java, this involves dozens of classes like BufferedReader, FileWriter, and endless try-catch blocks. But what about Kotlin? The language created by JetBrains doesn't just "simplify" Java — it rethinks data handling. Here, files are opened with a single line of code, and errors are handled elegantly.
In this article, we will cover working with files and the file system in Kotlin from basic operations to advanced techniques. You will learn how to read and write files, manage directories, work with JSON and CSV, and avoid common mistakes. Ready? Let's go!
\n\nBasics: How Kotlin simplifies Java IO
\n\nThe main feature of Kotlin is extension functions. Instead of writing dozens of lines to read a file, you use a single function like .readText() or .readLines(). Let's look at specific examples.
Reading an entire file
\nThe simplest way to read a text file is to use readText(). This method returns a string with the file's contents. Important: the file is automatically closed after reading.
import java.io.File\n\nfun main() {\n val content = File("example.txt").readText()\n println(content)\n}\n\nIf the file is large, it's better to read it line by line:
\n\nval lines = File("example.txt").readLines()\nlines.forEach { line -> println(line) }\n\nAnd if you need to read it as a stream (for example, to process large logs):
\n\nFile("example.txt").useLines { lines ->\n lines.forEach { line ->\n // Process each line\n println(line)\n }\n}\n\nNotice useLines() — this is a function that guarantees the resource is closed after the block completes. It looks magical but works like try-with-resources in Java, only shorter.
Writing to a file
\nWriting data in Kotlin is also intuitive. Here are three options:
\n\n// 1. Overwrite the file\nFile("output.txt").writeText("Hello, world!")\n\n// 2. Append to the end of the file\nFile("output.txt").appendText("New line\\n")\n\n// 3. Write a list of strings\nval list = listOf("line1", "line2", "line3")\nFile("output.txt").writeText(list.joinToString("\\n"))\n\nBy the way, if you are working with binary data (images, archives), use writeBytes() and readBytes():
val imageBytes = File("image.png").readBytes()\n// Process bytes...\nFile("copy.png").writeBytes(imageBytes)\n\nManaging the file system: creating, deleting, moving
\n\nKotlin doesn't reinvent the wheel, but it makes working with the file system more convenient. All operations are available through the java.io.File class with extensions.
Creating directories
\nTo create a directory (and all parent directories if they don't exist):
\n\nval dir = File("data/logs/2024")\nval created = dir.mkdirs() // Returns true if created\nprintln("Directory created: $created")\n\nNote: mkdir() creates only the last folder, while mkdirs() creates the entire chain.
Checking existence and deleting
\n\nval file = File("temp.txt")\nif (file.exists()) {\n if (file.delete()) {\n println("File deleted")\n } else {\n println("Could not delete")\n }\n}\n\n// Recursive deletion of a directory\nFile("data").deleteRecursively()\n\nBe careful with deleteRecursively() — this is an irreversible operation. Double-check that you are deleting the right data.
Moving and copying
\n\nval source = File("old.txt")\nval target = File("new_dir/new.txt")\n\n// Moving (per