How does error reporting differ between File and Files, and how would you handle the case where a file might legitimately not exist?
answer
- File = boolean (fail-silent); Files = typed exceptions (fail-loud)
- NoSuchFile / AccessDenied / DirectoryNotEmpty / FileAlreadyExists
- deleteIfExists: false on absent, still throws on real errors
- avoid exists()+delete() (TOCTOU)
basics
~20 sFile methods return false when something goes wrong, so you don't know the reason. Files methods throw specific exceptions instead. If a file might legitimately be absent, use Files.deleteIfExists, which returns true/false without throwing for the missing case.
solid answer
~40 sThe core difference is fail-silent vs fail-loud. File.delete()/mkdir()/renameTo() return a boolean; on failure you just get false and lose the cause — missing file, permissions, locked, non-empty directory all look identical. Files instead throws typed IOExceptions: NoSuchFileException, AccessDeniedException, DirectoryNotEmptyException, FileAlreadyExistsException, so you can branch on the real problem. For the genuinely-optional case where absence is normal and not an error, NIO.2 provides convenience variants that return a boolean by design: Files.deleteIfExists(path) returns false if it wasn't there but still throws on real errors like permissions. That's the right tool — it distinguishes 'expected absence' from 'unexpected failure', which a bare File.delete() can never do.
go deeper
Knows Files throws while File returns a boolean, and that deleteIfExists handles the optional-file case.
Names several specific exception subtypes and picks delete vs deleteIfExists by whether absence is an error.
Designs error handling that catches the specific cause, distinguishes expected absence from real failure, and avoids exists()+act races.
Sets codebase conventions around fail-loud I/O, reasons about atomicity/TOCTOU and how silent failures hide data-loss bugs in production.
## The two failure philosophies **Fail-silent (File).** Legacy `java.io.File` mutating methods signal failure with a `boolean` return: ```java boolean deleted = file.delete(); // false = something went wrong, but WHAT? boolean made = dir.mkdir(); // false = couldn't create — why? boolean moved = a.renameTo(b); // false = move failed — why? ``` A `false` collapses *every* possible cause into one value: the path didn't exist, you lacked permission, the directory wasn't empty, the target already existed, a lock was held. The caller cannot recover intelligently and often just ignores the return value entirely — a classic source of silent data bugs. **Fail-loud (Files).** NIO.2's `Files` throws a *specific* subclass of `IOException` describing the cause: - `NoSuchFileException` — the path doesn't exist. - `AccessDeniedException` — permission denied. - `DirectoryNotEmptyException` — tried to delete a non-empty directory. - `FileAlreadyExistsException` — create/copy target already exists (without `REPLACE_EXISTING`). - `AtomicMoveNotSupportedException` — atomic move requested but the filesystem can't do it. Because these are distinct types, you can `catch` exactly the case you care about and let others propagate. ## Handling 'might legitimately be absent' Sometimes absence is **not** an error — e.g. deleting a temp file that may already be gone. Throwing `NoSuchFileException` there would force ugly try/catch. NIO.2 anticipates this with **boolean-returning convenience methods** that are *deliberately* lenient about the absence case while still throwing on real failures: ```java // Returns false if the file wasn't there (no exception), // but still THROWS AccessDeniedException / DirectoryNotEmptyException on real problems. boolean removed = Files.deleteIfExists(path); ``` Contrast the three options: | Need | Use | |---|---| | Absence is an error | `Files.delete(path)` (throws `NoSuchFileException`) | | Absence is fine, other failures aren't | `Files.deleteIfExists(path)` (returns boolean, throws on real errors) | | You truly don't care about any reason (legacy) | `file.delete()` (boolean, swallows everything) | The key insight: `deleteIfExists` is **not** the same as the old fail-silent `File.delete()`. It only forgives the *expected* absence; genuine errors still surface. That distinction — expected absence vs unexpected failure — is exactly what the old boolean API could never express. ## A note on checking-then-acting (TOCTOU) Avoid `if (Files.exists(p)) Files.delete(p);`. Between the check and the act the file can change (a **time-of-check-to-time-of-use** race), and you can still hit `NoSuchFileException`. `deleteIfExists` (or just delete-and-catch) does the check atomically inside one call and is the safer idiom.
- Why is 'if Files.exists(p) then Files.delete(p)' discouraged?It's a time-of-check-to-time-of-use (TOCTOU) race: the file can be created or removed between the check and the delete, so you can still get NoSuchFileException or delete the wrong thing. Files.deleteIfExists does the check-and-act atomically.
saying these in an interview costs you the question
- Treating Files.deleteIfExists as identical to the old fail-silent File.delete() — it still throws on permission/non-empty errors.
- Using Files.exists() then Files.delete() (TOCTOU race) instead of an atomic call.
- Catching generic IOException when you actually want to handle just NoSuchFileException differently.
- Assuming File.delete() throws — it never does.