skip to content

I/O & Files

kotlin.io wraps Java's file and stream APIs in extensions that read, write, walk, and — most importantly — close deterministically. The resource-closing question is the one that always comes up.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

explore

questions

15

Given a java.io.File in Kotlin, how do you get the file's extension and its name without the extension? Name the kotlin.io extension properties and explain what they return for tricky names like "archive.tar.gz" and a dotfile like ".gitignore".

level: juniorimportance: must knowfreq 55%

answer

  1. extension = after last dot; "" if none
  2. nameWithoutExtension = before last dot
  3. Splits on LAST dot only (tar.gz -> gz)
  4. .gitignore -> extension "gitignore", name ""
  5. Pure string ops, no disk, no lowercasing

basics

~10 s

Use the file's extension property to get the part after the last dot, and nameWithoutExtension to get the name with that part removed. They are Kotlin helpers added on top of java.io.File.

solid answer

~30 s

kotlin.io adds extension properties on java.io.File: File.extension returns the substring after the last '.' in the file name (empty string if there is no dot), and File.nameWithoutExtension returns name with everything from the last '.' removed. They operate on name only, not the path. For "archive.tar.gz", extension is "gz" and nameWithoutExtension is "archive.tar" — they only split on the LAST dot. For a dotfile ".gitignore", the leading dot is treated as the separator, so extension is "gitignore" and nameWithoutExtension is "". These are pure string operations: they don't touch the filesystem and don't lowercase, so comparisons should normalize case yourself.

code

kotlin · 9 lines
kotlin
import java.io.File

val f = File("/data/archive.tar.gz")
println(f.name)                 // archive.tar.gz
println(f.extension)            // gz
println(f.nameWithoutExtension) // archive.tar

// Robust extension check:
fun isImage(file: File) = file.extension.lowercase() in setOf("png", "jpg", "jpeg")

go deeper

for a junior

Knows extension and nameWithoutExtension exist and what each returns for a simple name.

for a middle

Handles edge cases: empty string for no dot, last-dot-only on tar.gz, dotfiles, case normalization.

for a senior

Notes they are pure string extension properties on java.io.File, never null, no I/O; picks them over manual substring parsing.

for a principal

Frames when name-based parsing is insufficient (content sniffing / MIME detection) and why stdlib stays deliberately simple and filesystem-free.

## What these are Kotlin's standard library (`kotlin.io`) decorates `java.io.File` with **extension properties** — functions written `val File.x` that Kotlin lets you call as if they were members. Two of them parse the file *name*: - **`File.extension`** — the part of the name *after the last dot*. - **`File.nameWithoutExtension`** — the name with everything *from the last dot onward* removed. They operate purely on `File.name` (the last path segment) as a **string**. They never touch the disk, never read the file, and never lowercase anything. ## The exact rules Given the file name string: - `extension` = substring **after** the last `'.'`. If there is **no dot**, it returns the **empty string** `""` (not null). - `nameWithoutExtension` = substring **before** the last `'.'`. If there is no dot, it returns the whole name. Because the split is on the **last** dot only: ```kotlin import java.io.File File("archive.tar.gz").extension // "gz" File("archive.tar.gz").nameWithoutExtension // "archive.tar" File("README").extension // "" (no dot) File("README").nameWithoutExtension // "README" File(".gitignore").extension // "gitignore" (leading dot = separator) File(".gitignore").nameWithoutExtension // "" File("/a/b/data.csv").extension // "csv" (path is ignored, only name) ``` ## Gotchas - **No null.** A file with no extension yields `""`, so `if (file.extension == "")` not `== null`. - **Dotfiles** like `.env` / `.gitignore` count the leading dot as the separator, giving a surprising extension and an empty `nameWithoutExtension`. - **Multi-part extensions** (`.tar.gz`) only lose the last segment. - **Case sensitivity** — `"IMG.PNG".extension` is `"PNG"`; normalize with `.lowercase()` before comparing. - These are **String parsers**, so they work even if the file doesn't exist on disk. Related helpers in the same family: `File.name` (last segment with extension) and `File.path`/`File.absolutePath`.

  • What does File("noext").extension return — null or empty string?
    Empty string "". These properties never return null; check with isEmpty() or == "".
  • Do extension and nameWithoutExtension look at the whole path?
    No, only File.name (the last segment). The directory portion is ignored.

Like splitting a person's email at the last '@' or a filename at its trailing tag — only the final marker counts.

saying these in an interview costs you the question

  • Saying extension returns null when there is no dot
  • Claiming "archive.tar.gz".extension is "tar.gz"
  • Thinking these read the file from disk
  • Assuming the result is lowercased automatically
  • Confusing File.name with File.nameWithoutExtension

context

open as a page

How do you read an entire file into a String and write a String to a file in Kotlin using the standard library? Name the relevant File extension functions.

level: juniorimportance: must knowfreq 75%

basics

~10 s

Use File("path").readText() to get the whole file as one String, and File("path").writeText("content") to replace the file's contents. Both are Kotlin extension functions that open and close the file for you.

open as a page

What does the Kotlin standard library `use { }` function do, and why use it instead of manually calling `close()`?

level: juniorimportance: must knowfreq 70%

basics

~10 s

use { } runs your code with a resource (like a file or stream) and automatically closes it afterwards, even if your code throws an error. It saves you from forgetting to close it.

open as a page

A teammate uses File.readLines() to process a 5 GB log file and the process runs out of memory. How would you fix it with the Kotlin stdlib, and why does that fix work?

level: middleimportance: must knowfreq 65%

basics

~20 s

readLines loads every line into memory at once. Replace it with useLines or forEachLine, which read the file line by line and only keep one line in memory at a time, so a huge file no longer blows up the heap.

open as a page

What does File.resolve (and resolveSibling) do in kotlin.io, and how does it differ from new File(parent, child) and from string concatenation when building paths? What happens when the child is an absolute path?

level: middleimportance: should knowfreq 45%

basics

~10 s

resolve joins a base directory with a child path safely, using the right separator. resolveSibling joins against the parent. If the child is already absolute, resolve just returns the child instead of nesting it.

open as a page

Show how to read a file line by line using bufferedReader().use, and explain what .use guarantees and why you'd choose it over readText.

level: middleimportance: should knowfreq 55%

basics

~20 s

Call file.bufferedReader().use { reader -> ... }. The use block runs your code and then always closes the reader, even if an error happens. You choose it over readText when the file is too big to load all at once.

open as a page

How does `use` propagate its return value, and how do you correctly handle two resources (e.g. an input and an output stream) with `use`?

level: middleimportance: should knowfreq 55%

basics

~10 s

use returns whatever its lambda returns, so you can assign it directly. For two resources, nest one use inside another so both close in the right order.

open as a page

If both the `use` block and the resource's `close()` throw exceptions, which exception propagates, and what happens to the other?

level: middleimportance: should knowfreq 40%

basics

~10 s

The error from your block wins and is thrown. The error from closing is attached to it as a 'suppressed' exception so you don't lose it, but it doesn't replace the original.

open as a page

Explain File.copyTo, File.copyRecursively, and File.deleteRecursively in kotlin.io: their overwrite semantics, return values, error handling, and the key caveats (atomicity, the missing-file behavior) you must warn a team about.

level: seniorimportance: should knowfreq 35%

basics

~20 s

copyTo copies one file to a target; copyRecursively copies a whole folder; deleteRecursively deletes a folder and everything in it. By default copyTo refuses to overwrite an existing target and throws; pass overwrite = true to replace it. deleteRecursively returns true/false instead of throwing.

open as a page

How do you recursively traverse a directory tree in Kotlin using only kotlin.io? Compare walkTopDown and walkBottomUp, explain what a FileTreeWalk is, and how to filter, limit depth, and handle errors during the walk.

level: seniorimportance: should knowfreq 40%

basics

~20 s

Call directory.walkTopDown() or walkBottomUp() to get a lazy sequence of every file and folder under it. Top-down visits a folder before its contents; bottom-up visits contents first. You can filter, limit depth, and handle errors with builder methods.

open as a page

What charset do Kotlin's File text helpers use by default, and what concrete bugs arise if a file's actual encoding differs from what you read it with?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Kotlin's readText, writeText, readLines, etc. default to UTF-8. If a file is actually in another encoding (like Latin-1 or UTF-16), reading it as UTF-8 corrupts non-ASCII characters or throws, because the bytes are decoded with the wrong rules.

open as a page

Contrast `use` with a manual `try/finally` and with scope functions like `apply`/`also`/`let`. When would you NOT reach for `use`?

level: seniorimportance: should knowfreq 45%

basics

~20 s

use is a focused try/finally that closes a resource; it isn't a general scope function. Don't use it for objects that aren't Closeable, or when the resource must outlive the block (e.g. you return it or a framework owns its lifecycle).

open as a page

What goes wrong when you return a lazy `Sequence` (e.g. from `lineSequence()`) out of a `use` block, and how do you consume file lines safely?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

A lazy sequence only reads when you iterate it. If you return it from use, the reader is already closed before you read, so iteration fails. Consume the lines inside the block, or use useLines.

open as a page

When should a team prefer kotlin.io's File extensions (resolve/walk/copyRecursively/deleteRecursively) versus java.nio.file (Path, Files, walkFileTree)? Justify the trade-offs an architect cares about.

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Use Kotlin's File helpers for quick, readable tasks like tests, scripts, and tooling. Use java.nio.file when you need atomic moves, file attributes, symlink control, multiple filesystems, or strict error handling — it makes stronger guarantees.

open as a page

You're building a service that ingests user-uploaded files of unknown, possibly huge size and writes processed output that must be durable. Critique using File.readText/writeText here and describe what you'd reach for instead and why.

level: principalimportance: nice to knowfreq 25%

basics

~20 s

readText/writeText load or write the whole file at once and don't guarantee the data is safely saved to disk. For untrusted, possibly huge files you should stream and use atomic, flushed writes so you don't run out of memory or leave a half-written file.

open as a page