When should you choose java.nio.file.Path/Files over the legacy java.io.File, and how do you interoperate between them?
answer
- File = Java 1.0 path+ops; NIO.2 = Path (pathname) + Files (ops)
- Files throws specific IOExceptions; File returns boolean
- NIO.2: symlinks, attributes, atomic move, lazy walk, custom FS
- Convert: file.toPath() / path.toFile()
- Custom-FS Path.toFile() throws UnsupportedOperationException
basics
~20 sPrefer Path/Files (NIO.2, Java 7+) for new code: it throws clear exceptions instead of returning booleans, supports symlinks, file attributes, atomic moves and lazy directory streams. Use File only for legacy APIs. Convert with file.toPath() and path.toFile().
solid answer
~40 sjava.io.File is the original (Java 1.0) path abstraction; java.nio.file (NIO.2, Java 7) replaces it with Path (the pathname) plus the Files utility class (operations). Choose Path/Files for almost all new code because it fixes File's core weaknesses: operations throw specific IOExceptions (NoSuchFileException, AccessDeniedException, DirectoryNotEmptyException) instead of returning an opaque boolean; it is symlink-aware (LinkOption.NOFOLLOW_LINKS); it exposes rich attributes (creation time, owner, POSIX permissions) via attribute views; it supports atomic moves, copy options, lazy directory streams (newDirectoryStream), recursive walks (Files.walk), and a pluggable FileSystem SPI (e.g. zip filesystems). Keep File only where a legacy API demands it. Interop is trivial and lossless: file.toPath() and path.toFile(); File also implements comparable path methods. A common pattern is to take File at an API boundary, immediately toPath(), and use Files internally.
code
java · 27 linesimport java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
// Boundary takes a legacy File; switch to NIO.2 immediately:
void process(File legacy) throws IOException {
Path p = legacy.toPath(); // File -> Path (lossless)
BasicFileAttributes a = Files.readAttributes(p, BasicFileAttributes.class);
System.out.println("size=" + a.size() + " created=" + a.creationTime());
// Clear errors instead of opaque booleans:
try {
Files.move(p, p.resolveSibling("done.txt"),
StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
} catch (NoSuchFileException e) {
// specific, actionable
}
// Lazy, closeable, filtered listing:
try (DirectoryStream<Path> ds = Files.newDirectoryStream(p.getParent(), "*.csv")) {
for (Path child : ds) System.out.println(child.getFileName());
}
File back = p.toFile(); // Path -> File only if a legacy API needs it
}go deeper
Knows NIO.2 (Path/Files) is the modern replacement and that you convert with toPath()/toFile().
Lists concrete advantages (exceptions over booleans, attributes, directory streams) and adopts Files for real operations.
Articulates the full tradeoff (errors, symlinks, atomicity, attribute views, scalable traversal, custom filesystems) and applies the boundary-conversion pattern.
Considers the FileSystem SPI (zip/in-memory FS for tests), portability/atomicity guarantees, and sets an org-wide policy to default to NIO.2.
### Two generations of the same idea Java has **two** file-system path APIs: - **`java.io.File`** (since Java 1.0): a single class that is *both* the pathname abstraction *and* the operations (create/delete/list/metadata). - **`java.nio.file`** — **NIO.2** (since Java 7, JSR 203): splits the concerns into **`Path`** (an immutable pathname, obtained via `Path.of(...)` / `Paths.get(...)` / a `FileSystem`) and **`Files`** (a stateless utility class of static methods that *act* on `Path`s). ### Why prefer Path/Files 1. **Real error reporting.** Where `File.delete()`/`renameTo()`/`mkdir()` return a bare `boolean`, `Files.delete`/`move`/`createDirectories` **throw informative exceptions** — `NoSuchFileException`, `FileAlreadyExistsException`, `DirectoryNotEmptyException`, `AccessDeniedException`. You learn *why* it failed. 2. **Symlink awareness.** `File` silently follows links and can't tell you it's a link. NIO.2 distinguishes them (`Files.isSymbolicLink`, `LinkOption.NOFOLLOW_LINKS`, `Files.createSymbolicLink`, `readSymbolicLink`). 3. **Rich, consistent attributes.** `BasicFileAttributes` (creation/access/modified time, size, type) read in one snapshot; `PosixFileAttributes` / `DosFileAttributes` / `AclFileAttributeView` for owner, group, permission bits — none of which `File` exposes. 4. **Atomicity & options.** `Files.move(..., ATOMIC_MOVE, REPLACE_EXISTING)`, `Files.copy(..., COPY_ATTRIBUTES)` — explicit, portable semantics versus `renameTo`'s 'platform-dependent' shrug. 5. **Scalable traversal.** `Files.newDirectoryStream` (lazy, closeable, glob filter), `Files.list`/`walk`/`find` (Streams with depth + `FileVisitOption`), and `Files.walkFileTree` (the visitor pattern) versus `listFiles()` returning a fully-materialized array (or `null`). 6. **Convenience I/O.** `Files.readAllBytes`, `readString`, `writeString`, `newBufferedReader/Writer`, `lines` — concise content helpers. 7. **Pluggable FileSystems (SPI).** A `Path` can live in a non-default `FileSystem` — e.g. a **zip/jar filesystem** (`FileSystems.newFileSystem(zip)`), or an in-memory FS for tests. `File` is hard-wired to the default OS filesystem. 8. **WatchService.** NIO.2 adds directory change watching, which `File` has no equivalent for. ### When File is still acceptable - A **third-party or legacy API** takes/returns `File` — then use it at the boundary. - A quick, throwaway boolean check (`new File(p).isDirectory()`) where ambiguity doesn't matter. Neither case justifies building *new* logic on `File`. ### Interoperability — cheap and lossless - `File` → `Path`: **`file.toPath()`**. - `Path` → `File`: **`path.toFile()`** (works for default-filesystem paths; a Path in a *custom* FileSystem like zip throws `UnsupportedOperationException` on `toFile()`). Both conversions preserve the pathname, so you can adopt NIO.2 incrementally: accept `File` at the edge, immediately `toPath()`, do everything with `Files`, convert back only if a downstream API demands a `File`. ### Decision summary New code → **`Path`/`Files`** (clear errors, attributes, symlinks, atomic ops, scalable traversal, custom filesystems). **`File`** only to satisfy legacy signatures or trivial boolean probes — and even then, convert with `toPath()` as soon as real work begins.
- When can path.toFile() throw, and why?When the Path belongs to a non-default FileSystem (e.g. a zip/jar or in-memory filesystem). java.io.File only models the default OS filesystem, so there's no valid File for such a Path — it throws UnsupportedOperationException.
- Name two capabilities NIO.2 has that java.io.File completely lacks.Examples: symlink creation/inspection, reading creation time/owner/POSIX permissions via attribute views, atomic moves, lazy directory streams and recursive Files.walk, a WatchService for change notifications, and pluggable FileSystems (zip/in-memory).
saying these in an interview costs you the question
- Building new logic on File when Path/Files is available
- Claiming File and Path are interchangeable for error handling
- Assuming path.toFile() always works (fails for non-default filesystems)
- Thinking File supports symlinks, creation time, or atomic moves
- Believing NIO.2 is only about performance rather than correctness/expressiveness