Kotlin Files and I/O

Read and write text files in Kotlin with File, readText, readLines, writeText and appendText, process big files lazily with useLines, close resources with use, and parse CSV and JSON-like data.

Lesson 24 of 43Intermediate18 min read

Programs that forget everything when they close have limited uses. Saving notes, keeping a log, importing a spreadsheet exported as CSV, exporting a report, loading settings: all of these need files.

Kotlin runs on the JVM, so it can use Java's File class, but it adds a set of friendly extension functions in kotlin.io that make common jobs one line: file.readText(), file.writeText(text), file.forEachLine { }. For big files there's useLines, which reads one line at a time, and for anything that must be closed afterwards there's use, which closes it automatically even if something goes wrong.

In this lesson you'll read and write whole files, append to a log, process a large file without loading it all into memory, parse CSV lines into data classes, work with folders, and handle the errors that file operations can always produce. On Android, files live in the app's private storage, but the same functions apply.

Reading and writing whole files

Kotlin
import java.io.File

val file = File("notes.txt")
file.writeText("Buy milk\nCall Sam\n")     // creates or replaces the file
file.appendText("Book dentist\n")          // adds to the end
val text = file.readText()                 // the whole file as one String
val lines = file.readLines()               // a List<String>, one per line
  • writeText replaces the whole file; appendText adds to it.
  • These functions use UTF-8 by default, which handles every language's characters.
  • A relative path like "notes.txt" is found from the folder where the program runs.
  • They're perfect for small and medium files. For very large ones, read line by line instead.

Large files: forEachLine and useLines

readLines() loads every line into memory at once, which is a problem for a 2 GB log file. Read lazily instead:

Kotlin
file.forEachLine { line -> if ("ERROR" in line) println(line) }

val errorCount = file.useLines { lines -> lines.count { "ERROR" in it } }

useLines gives you a sequence of lines, so you can use operators like filter, map and count on it, and it closes the file when the block finishes. Only the current line is held in memory, however big the file is.

use, writers and folders

Anything that holds a file open, such as a writer or reader, must be closed, or data may never reach the disk. The use function closes it automatically, even when an exception is thrown:

Kotlin
File("report.csv").bufferedWriter().use { out ->
    out.write("name,score")
    out.newLine()
}

Useful File members: exists(), isDirectory, length(), delete(), mkdirs() (create a folder and its parents), name, extension and listFiles(). File("photos").walk() visits every file in a folder tree, which is handy for scripts that tidy or count files.

Errors and parsing data

File operations can fail: the file is missing, locked or unreadable. Missing files throw FileNotFoundException, a kind of IOException. Check with exists() first, or wrap the operation in try/catch or runCatching from the exceptions lesson.

To read structured text such as CSV, split each line and build a data class:

Kotlin
val products = file.readLines().drop(1).map { line ->
    val (name, price) = line.split(",")
    Product(name, price.toDouble())
}

For real CSV with quoted commas, or for JSON, use a library such as kotlinx.serialization.

The newer Path API in kotlin.io.path

java.io.File has been around since the earliest days of Java. Newer code often uses java.nio.file.Path instead, and Kotlin's kotlin.io.path package gives it the same friendly functions you've seen on File:

Kotlin
import kotlin.io.path.*

val dir = Path("data")
dir.createDirectories()                  // no error if it already exists
val notes = dir / "notes.txt"            // data/notes.txt
notes.writeText("Buy milk\n")
notes.appendText("Call Sam\n")
println(notes.readLines())               // [Buy milk, Call Sam]
println(notes.name)                      // notes.txt
println(dir.listDirectoryEntries("*.txt").map { it.name })

The / operator joins path parts with the right separator for the operating system, which is safer than gluing strings together with "/" or "\\". With File, File("data", "notes.txt") does the same job.

One difference to know: reading a missing file through a Path throws NoSuchFileException, while File.readText() throws FileNotFoundException. Both are kinds of IOException, so catching IOException handles either. You can switch between the two styles at any time with file.toPath() and path.toFile().

Saving without corrupting the old file

If a program crashes halfway through writeText, the file can end up half-written, and the old contents are already gone. A common safeguard is to write to a temporary file next to the real one, then move it into place in a single step:

Kotlin
val tmp = dir / "notes.txt.tmp"
tmp.writeText(newContent)
tmp.moveTo(notes, overwrite = true)

Until the move happens, the original file is untouched, so readers see either the complete old version or the complete new one. Settings files and saved games are good candidates for this pattern.

Kotlin Files Syntax

File essentials

import java.io.File
val f = File("data.txt")
f.writeText("hello\n")
f.appendText("more\n")
f.readText()   f.readLines()
f.forEachLine { println(it) }
f.useLines { lines -> lines.count() }
f.bufferedWriter().use { it.write("x") }
f.exists()   f.delete()   File("dir").mkdirs()   File("dir").walk()

Kotlin Files Summary

  • File(path) represents a file; readText, readLines, writeText and appendText handle whole files.
  • writeText replaces content; appendText adds to the end.
  • forEachLine and useLines read large files one line at a time.
  • use { } closes writers and readers automatically, even after an exception.
  • File operations can throw IOException; check exists() or catch errors, and avoid absolute paths.

Kotlin Files Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Saving and loading notes

Kotlin
import java.io.File

fun main() {
    val file = File("notes.txt")
    file.writeText("Buy milk\nCall Sam\n")
    file.appendText("Book dentist\n")

    val notes = file.readLines()
    println("You have ${notes.size} notes:")
    notes.forEachIndexed { i, note -> println("${i + 1}. $note") }
    println("Exists? ${file.exists()}")
}
Output
You have 3 notes:
1. Buy milk
2. Call Sam
3. Book dentist
Exists? true
Line-by-line explanation
  1. Line 5writeText creates the file, or replaces it if it already exists.
  2. Line 6appendText adds a line to the end.
  3. Line 8readLines gives one String per line; the final newline doesn't create an empty extra line.
  4. Line 10forEachIndexed provides the position and the item.
Example 2

Counting errors in a log, lazily

Kotlin
import java.io.File

fun main() {
    val log = File("app.log")
    log.bufferedWriter().use { out ->
        out.write("INFO Started\n")
        out.write("ERROR Payment timeout\n")
        out.write("INFO Order 1042 saved\n")
        out.write("ERROR Disk nearly full\n")
    }

    val errors = log.useLines { lines -> lines.filter { it.startsWith("ERROR") }.toList() }
    println("${errors.size} errors:")
    errors.forEach { println("  ${it.removePrefix("ERROR ")}") }
}
Output
2 errors:
  Payment timeout
  Disk nearly full
Line-by-line explanation
  1. Lines 5 to 10use closes the writer when the block ends, so all the lines are saved.
  2. Line 12useLines reads one line at a time and closes the file afterwards.
  3. Line 14removePrefix strips the label for a cleaner message.
Example 3

Reading CSV into data classes

Kotlin
import java.io.File

data class Product(val name: String, val price: Double, val stock: Int)

fun loadProducts(path: String): List<Product> {
    val file = File(path)
    if (!file.exists()) return emptyList()
    return file.readLines()
        .drop(1)
        .filter { it.isNotBlank() }
        .map { line ->
            val (name, price, stock) = line.split(",")
            Product(name.trim(), price.toDouble(), stock.trim().toInt())
        }
}

fun main() {
    File("products.csv").writeText("name,price,stock\nPen,1.20,300\nBackpack,34.99,15\nLamp,24.50,22\n")
    val products = loadProducts("products.csv")
    val value = products.sumOf { it.price * it.stock }
    println("${products.size} products, stock worth £${"%.2f".format(value)}")
    println(loadProducts("missing.csv").size)
}
Output
3 products, stock worth £1423.85
0
Line-by-line explanation
  1. Line 7A missing file returns an empty list rather than crashing.
  2. Line 9drop(1) skips the header row.
  3. Line 12Destructuring splits each line into three named pieces.
  4. Line 22The missing file gives 0 products.

Common Kotlin Files Mistakes

Overwriting instead of appending

Don't
File("log.txt").writeText("New entry\n")     // previous entries are gone
Do
File("log.txt").appendText("New entry\n")

Forgetting to close a writer

Don't
val out = File("out.txt").bufferedWriter()
out.write("data")                            // may never reach the disk
Do
File("out.txt").bufferedWriter().use { it.write("data") }

readLines on a huge file

Don't
val count = File("huge.log").readLines().count { "ERROR" in it }   // loads gigabytes into memory
Do
val count = File("huge.log").useLines { lines -> lines.count { "ERROR" in it } }

Kotlin Files in Real Projects

Files in Kotlin projects

Command-line tools and build scripts written in Kotlin read configuration, process CSV exports and generate reports. Android apps save small files in private storage, cache downloaded images, and export data to share. Gradle's Kotlin build scripts use these same functions to copy and generate files during a build.

Kotlin Files FAQ

How do I check if a file exists in Kotlin?

Call File("notes.txt").exists(), or Path("notes.txt").exists() with kotlin.io.path. Bear in mind that the file could be deleted between the check and the read, so for important code it's still worth catching IOException around the read itself.

Where does Kotlin look for a relative file path?

Relative paths such as "notes.txt" are resolved from the working directory, the folder the program was started from, not the folder containing your source code. In an IDE that's usually the project folder. System.getProperty("user.dir") tells you the working directory at run time.

How do I read a file line by line in Kotlin?

For small files, File("data.txt").readLines() gives a List<String>. For large files, use forEachLine { } or useLines { lines -> ... }, which read one line at a time and close the file for you. All of them strip the line endings, so you don't need to trim \n yourself.

What is the difference between File and Path in Kotlin?

java.io.File is the older Java API; java.nio.file.Path is the newer one, with better error messages and support for file systems such as zip files. Kotlin adds extension functions to both, so readText, writeText and friends work the same way on either. New code can use Path with kotlin.io.path, but File is still everywhere and perfectly fine.

How do I list all files in a folder in Kotlin?

File("photos").listFiles() returns the files and folders directly inside, or null if the path isn't a readable folder. To include subfolders, use File("photos").walk(), a sequence you can filter, as in .filter { it.isFile && it.extension == "jpg" }. With paths, listDirectoryEntries("*.jpg") returns matching entries in one folder.

Kotlin Files Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the line that adds text to the end of a file.

File("log.txt").("Saved\n")
Multiple choiceeasy

What does File("a.txt").writeText("hi") do to an existing file?

Multiple choicemedium

Which is best for counting lines in a 5 GB file?

Multiple choicemedium

What does use { } guarantee?

Predict the outputhard

A file contains "a\nb\nc\n". What does readLines().size return?

Kotlin Files Quiz

5 questions to check what you learned.

  1. Which function reads a whole file into one String?

    • readLines
    • loadText
    • getText
    • readText
  2. Which exception means a file wasn't found?

    • NullPointerException
    • NumberFormatException
    • FileNotFoundException
    • IndexOutOfBoundsException
  3. What does File("photos/2024").mkdirs() do?

    • Creates a file
    • Creates the folder and any missing parent folders
    • Deletes the folder
    • Lists the folder
  4. Why avoid absolute paths like C:\Users\sam\data.csv?

    • They won't exist on other computers or devices
    • Kotlin can't read C drives
    • They're slower
    • Backslashes aren't allowed
  5. What does File("notes").walk() do?

    • Deletes every file
    • Moves the folder
    • Visits every file and folder inside notes, recursively
    • Reads the first file