skip to content

File Extensions & Walking

File extensions like resolve, extension, copyTo, and deleteRecursively cover path and file manipulation, and walkTopDown gives you a lazy tree traversal. They turn multi-line Java idioms into single expressions.

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

questions

5

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

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

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

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