skip to content

What file metadata can you read through java.io.File, and what are the pitfalls of methods like length(), lastModified(), exists(), and the can-read/write/execute checks?

level: middleimportance: should knowfreq 50%

answer

  1. length()=bytes, 0 for dirs/missing
  2. lastModified()=epoch millis, 0 = missing/unknown
  3. can* reflects the JVM process permissions now
  4. Ambiguous: 'no' vs 'couldn't tell'
  5. NIO.2 BasicFileAttributes = one snapshot + creation time

basics

~20 s

File can tell you if a path exists() and whether it's a isFile() or isDirectory(), its length() in bytes, lastModified() time in epoch milliseconds, and whether you canRead()/canWrite()/canExecute() it. Pitfall: many of these return 0 or false both for 'no' and for 'couldn't tell', so they're ambiguous.

solid answer

~40 s

Through File you query: existence (exists()), type (isFile(), isDirectory()), size (length(), bytes, 0 for directories or missing files), modification time (lastModified(), epoch millis, 0 if unknown), and access permissions (canRead/canWrite/canExecute, booleans). The central pitfall is ambiguity: length() returning 0 could mean an empty file, a directory, a non-existent path, or an I/O error; lastModified() returning 0 means 'doesn't exist or couldn't be read'; the can* checks reflect the JVM process's effective permissions at that instant and may be stale by the time you act (a TOCTOU race). They're also limited: no creation time, no owner, no symlink awareness, second-or-worse time resolution on some platforms. NIO.2's Files.readAttributes / BasicFileAttributes gives creation/access/modified times together, plus Files.exists/size/isReadable, and throws on errors instead of returning ambiguous zeros.

code

java · 22 lines
java
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;

File f = new File("/tmp/data.bin");

// Ambiguous with File: is 0 an empty file, a dir, or a missing path?
long len = f.length();          // 0 could mean several things
long mtime = f.lastModified();  // 0 = missing OR unreadable (and 1970 is legal too)
boolean writable = f.canWrite();// false could also mean 'does not exist'

// NIO.2: one consistent snapshot, throws on real error, more fields
Path p = f.toPath();
if (Files.exists(p)) {
    BasicFileAttributes a = Files.readAttributes(p, BasicFileAttributes.class);
    System.out.println(a.size());            // bytes
    System.out.println(a.creationTime());     // not available via File at all
    System.out.println(a.lastModifiedTime()); // FileTime, finer precision
    System.out.println(a.isDirectory());
}
// Prefer attempting the op over check-then-act to avoid TOCTOU races.

go deeper

for a junior

Knows exists(), isFile()/isDirectory(), length() and lastModified() and roughly what they return.

for a middle

Recognizes the ambiguity of 0/false returns and that can* reflects process permissions, not the human user.

for a senior

Explains TOCTOU races, missing fields (creation time, owner, symlink awareness), and switches to BasicFileAttributes for consistent snapshots.

for a principal

Reasons about syscall cost, race-free 'attempt-then-handle' patterns, and chooses attribute views/precision/symlink policy appropriate to the system.

### What metadata File exposes `java.io.File` lets you interrogate the file system about the path it represents: | Method | Returns | Meaning | |---|---|---| | `exists()` | boolean | Whether something exists at the path. | | `isFile()` | boolean | Exists **and** is a normal file. | | `isDirectory()` | boolean | Exists **and** is a directory. | | `isHidden()` | boolean | Platform notion of hidden (dot-file on Unix, hidden attr on Windows). | | `length()` | long | Size **in bytes**; unspecified/`0` for directories or non-existent paths. | | `lastModified()` | long | Last-modified time as **epoch milliseconds**; `0L` if the file doesn't exist or the time can't be read. | | `canRead()` / `canWrite()` / `canExecute()` | boolean | Whether the **JVM process** may read/write/execute the path now. | | `getName()` / `getParent()` / `getPath()` / `getAbsolutePath()` / `getCanonicalPath()` | String | Pathname components. | ### The pitfalls 1. **Ambiguous return values.** Because these methods don't throw, a single value conflates several conditions: - `length() == 0` → empty file? a directory? missing path? I/O error? You can't distinguish without extra checks (`exists()`, `isFile()`). - `lastModified() == 0` → 'doesn't exist **or** couldn't read the time'. Zero is also a *legal* timestamp (the Unix epoch, 1970), so it's genuinely ambiguous. - `canWrite() == false` → truly not writable, **or** the path doesn't exist. 2. **TOCTOU (Time-Of-Check to Time-Of-Use) races.** Checking `canWrite()` or `exists()` and then acting is racy — another process can change permissions or create/delete the file in between. The robust pattern is to **just attempt the operation and handle the failure**, not to pre-check. 3. **It reflects the process's effective permissions**, governed by OS ownership/ACLs and the user the JVM runs as — not necessarily what a human user could do. 4. **Coarse time resolution.** `lastModified()` is milliseconds in the API, but the underlying filesystem may only store second (or 2-second on old FAT) granularity, so it can be coarser than it looks. 5. **Missing metadata.** `File` exposes **no creation time, no last-access time, no owner/group, no POSIX mode bits** directly, and is **not symlink-aware** (it follows links transparently). 6. **Multiple separate calls = multiple syscalls + race windows.** Reading `length()`, then `lastModified()`, then `isDirectory()` hits the file system repeatedly and each may see a different state. ### The NIO.2 improvement `java.nio.file.Files` and **attribute views** address all of this: - `Files.readAttributes(path, BasicFileAttributes.class)` returns **creationTime, lastAccessTime, lastModifiedTime, size, isRegularFile/isDirectory/isSymbolicLink** in **one consistent snapshot**. - `PosixFileAttributes` adds owner/group/permission bits; `Files.getOwner` etc. - `Files.exists`, `Files.notExists`, `Files.isReadable`, `Files.size` throw or distinguish 'unknown' (`exists` vs `notExists` can *both* be false when access is denied). - `FileTime` carries sub-millisecond precision and is symlink-configurable (`LinkOption.NOFOLLOW_LINKS`). ### Practical guidance For a quick boolean (`isDirectory()`, `exists()`) `File` is fine. For sizes, times, permissions, or anything where the difference between 'no' and 'couldn't tell' matters — or where you need creation time/owner/symlink control — read attributes via `Files`. And prefer **try-the-operation** over **check-then-act** to avoid TOCTOU bugs.

  • Why is calling canWrite() before writing a file a fragile pattern?
    It's a TOCTOU race: permissions or existence can change between the check and the write, and the check itself can't distinguish 'not writable' from 'doesn't exist'. The robust approach is to attempt the write and catch the failure.
  • How do you get a file's creation time, which File can't provide?
    Use NIO.2: Files.readAttributes(path, BasicFileAttributes.class).creationTime(), which returns a FileTime (subject to filesystem support).

saying these in an interview costs you the question

  • Treating length()==0 as 'empty file' without checking exists()/isFile()
  • Assuming lastModified()==0 means 1970 rather than 'unknown/missing'
  • Relying on canWrite() then writing (TOCTOU race)
  • Expecting File to give creation time or file owner
  • Assuming millisecond precision regardless of filesystem

context