skip to content

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%

answer

  1. kotlin.io = convenience layer over java.io.File
  2. nio = atomicity, attributes, symlink control, alt filesystems
  3. FileTreeWalk lazy Sequence; deleteRecursively droppable Boolean
  4. Hybrid: nio core + Kotlin wrappers; File.toPath bridge
  5. nio FileSystem (Jimfs) -> in-memory testability

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.

solid answer

~40 s

kotlin.io's File extensions (resolve, walkTopDown/walkBottomUp, copyTo, copyRecursively, deleteRecursively) wrap the legacy java.io.File. They are concise, lazy (FileTreeWalk is a Sequence), and ideal for build tooling, tests, and scripts where convenience beats guarantees. They have known limits: operations aren't atomic, deleteRecursively returns a droppable Boolean and doesn't throw, attributes/permissions aren't preserved, and symlink/TOCTOU handling is weak. java.nio.file (Path, Files, FileSystems) gives atomic moves (ATOMIC_MOVE), copy options (REPLACE_EXISTING, COPY_ATTRIBUTES), symlink-aware operations (NOFOLLOW_LINKS), pluggable filesystems (zip/jimfs for testing), and richer exceptions. Architecturally: pick java.io+kotlin.io for low-stakes local I/O; pick java.nio for production data-integrity paths, cross-platform/cross-filesystem code, and testability via in-memory filesystems. A common pattern is using nio under the hood and exposing small Kotlin helpers.

code

kotlin · 12 lines
kotlin
import java.io.File
import java.nio.file.*

// Tooling: terse and lazy with kotlin.io
File("build").walkTopDown().filter { it.extension == "class" }.forEach { it.delete() }

// Production data path: atomic + attribute-preserving with nio
fun publish(src: java.nio.file.Path, dst: java.nio.file.Path) {
    val tmp = Files.createTempFile(dst.parent, "pub", ".tmp")
    Files.copy(src, tmp, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING)
    Files.move(tmp, dst, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
}

go deeper

for a junior

Knows Kotlin's File helpers exist and that java.nio is an alternative.

for a middle

Can name a couple of differences (atomic move, copy options) and pick kotlin.io for scripts.

for a senior

Articulates the guarantee gaps (atomicity, attributes, symlinks, Boolean returns) and chooses per use case.

for a principal

Sets a team policy: convenience layer for tooling, nio for data-integrity/cross-FS/testable paths; advocates a hybrid wrapper and in-memory FileSystem testing.

## Two layers, one job There are two filesystem APIs on the JVM, and Kotlin sits on top of the older one: - **`java.io.File` + `kotlin.io` extensions** — the convenient layer. `resolve`, `resolveSibling`, `extension`, `nameWithoutExtension`, `walkTopDown`/`walkBottomUp` (a lazy `FileTreeWalk : Sequence<File>`), `copyTo`, `copyRecursively`, `deleteRecursively`, plus `readText`/`writeText` (siblings). - **`java.nio.file`** — `Path`, `Files`, `FileSystems`, `walkFileTree`/`Files.walk`. The newer, lower-level, guarantee-rich layer. ## What kotlin.io is great at - **Readability & brevity**: `dir.walkTopDown().filter { it.extension == "kt" }` reads like prose. - **Laziness**: `FileTreeWalk` is a `Sequence`, so `first`/`take` short-circuit huge trees. - **Zero ceremony** for build scripts, code generators, tests, and one-off tooling. ## Where it falls short (and nio wins) | Concern | kotlin.io / java.io | java.nio.file | |---|---|---| | **Atomic move/replace** | none (copyTo not atomic) | `Files.move(..., ATOMIC_MOVE, REPLACE_EXISTING)` | | **Copy options** | overwrite Boolean only | `REPLACE_EXISTING`, `COPY_ATTRIBUTES`, `NOFOLLOW_LINKS` | | **Attributes/permissions** | not preserved by copyTo | `Files.copy(..., COPY_ATTRIBUTES)`, POSIX perms | | **Error signalling** | `deleteRecursively` returns droppable Boolean, no throw | rich typed exceptions per operation | | **Symlink control / TOCTOU** | weak | explicit `NOFOLLOW_LINKS`, `walkFileTree` visitor | | **Alternate filesystems** | local only | zip/jar FS, in-memory (Jimfs) for tests | ## Architectural decision guide - **Use kotlin.io / java.io when**: the I/O is **local and low-stakes** — tests, scripts, build tooling, dev utilities — and convenience/readability dominate. - **Use java.nio.file when**: you need **data integrity** (atomic temp-then-rename), **attribute/permission fidelity**, **symlink-safe** recursion on untrusted trees, **cross-filesystem** code, or **testability** through an in-memory `FileSystem`. - **Hybrid**: implement core operations on `nio`, expose ergonomic Kotlin wrappers; `File.toPath()` / `Path.toFile()` bridge the two cheaply. ## Testability angle (often overlooked) `java.io.File` always hits the **real** disk, making tests slow and order-dependent. `java.nio.file.FileSystem` abstractions (e.g., Jimfs) let you run filesystem logic **in memory**, which is a strong reason to write production file code against `Path`/`Files` rather than `File`. ## Bottom line Kotlin's `File` extensions are a productivity layer, not a correctness layer. Reach for them freely in tooling; for anything where a partial copy, lost permission bit, or ignored delete failure would matter, design against `java.nio.file`. ```kotlin // Atomic, attribute-preserving replace via nio (what copyTo can't guarantee): import java.nio.file.* val tmp = Files.createTempFile(dst.parent, "t", ".part") Files.copy(src, tmp, StandardCopyOption.COPY_ATTRIBUTES) Files.move(tmp, dst, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING) ```

  • Which API lets you replace a file atomically and why does that matter?
    java.nio.file via Files.move with ATOMIC_MOVE; it prevents readers from ever seeing a half-written file after a crash.
  • Why might production file code be written against Path/Files even for local disk?
    Testability: a FileSystem abstraction (e.g., Jimfs) runs the logic in memory, plus you get attribute and symlink control.
  • How do you move between the two APIs?
    File.toPath() and Path.toFile() convert cheaply, so you can mix kotlin.io ergonomics with nio guarantees.

kotlin.io is a Swiss Army knife for everyday cuts; java.nio is the machine shop you move to when tolerances and safety matter.

saying these in an interview costs you the question

  • Claiming kotlin.io copyTo/move is atomic
  • Defaulting to java.io.File for data-integrity-critical writes
  • Unaware nio offers ATOMIC_MOVE/COPY_ATTRIBUTES/NOFOLLOW_LINKS
  • Not considering in-memory FileSystem for testability
  • Treating the two layers as interchangeable with equal guarantees

context