File Class
The java.io.File class is Java's original file system abstraction, present since Java 1.0. A File object represents an abstract pathname — a string denoting a file or directory that may or may not exist on the file system. File objects are immutable: once constructed, the path string they represent never changes. The class provides a comprehensive set of methods for path manipulation, file system queries, directory operations, and file creation and deletion. File served as the primary file system API for 17 years until NIO.2's Path and Files classes superseded it in Java 7. Understanding File is essential for reading existing Java codebases, working with older APIs that accept File parameters, and understanding why NIO.2 was designed the way it was. This entry covers the complete File API in depth: all constructor forms and path semantics, every query and mutation method with its exact return and failure semantics, the listFiles() filtering API, path resolution and relative path handling, platform-specific behavior differences, the interoperability bridge between File and Path, and a precise catalog of File's deficiencies that motivated NIO.2.
Construction, Path Semantics, and Navigation Methods
// ── All four constructors ────────────────────────────────────────────
File f1 = new File("/home/user/data.txt"); // absolute path
File f2 = new File("data.txt"); // relative to CWD
File f3 = new File("subdir", "data.txt"); // parent string + child
File f4 = new File(new File("/home/user"), "data.txt"); // parent File + child
File f5 = new File(URI.create("file:///home/user/data.txt")); // from URI
// All path separators:
System.out.println(File.separator); // "/" on Unix, "\" on Windows
System.out.println(File.separatorChar); // '/' or '\'
System.out.println(File.pathSeparator); // ":" on Unix, ";" on Windows (for PATH lists)
// Forward slash works on Windows too:
File winFile = new File("C:/Users/user/data.txt"); // works on Windows
// ── Naming methods ─────────────────────────────────────────────────────
File f = new File("/home/user/documents/report.txt");
System.out.println(f.getName()); // report.txt
System.out.println(f.getParent()); // /home/user/documents
System.out.println(f.getParentFile()); // File for /home/user/documents
System.out.println(f.getPath()); // /home/user/documents/report.txt (as constructed)
System.out.println(f.isAbsolute()); // true
System.out.println(f.getAbsolutePath()); // same (already absolute)
// Relative path — getAbsolutePath() prepends CWD:
File rel = new File("data.txt");
System.out.println(rel.getPath()); // data.txt
System.out.println(rel.getAbsolutePath()); // /current/working/dir/data.txt
System.out.println(rel.isAbsolute()); // false
// getCanonicalPath — filesystem access, resolves symlinks:
try {
File symlink = new File("/home/user/link"); // symlink → /data/actual
System.out.println(symlink.getAbsolutePath()); // /home/user/link (path string)
System.out.println(symlink.getCanonicalPath()); // /data/actual (resolved)
} catch (IOException e) { /* I/O error — only method that can throw */ }
// With ../ and ./:
try {
File dotdot = new File("/home/user/../user/./documents");
System.out.println(dotdot.getAbsolutePath()); // /home/user/../user/./documents
System.out.println(dotdot.getCanonicalPath()); // /home/user/documents
} catch (IOException e) { }
// ── Path navigation without Path.resolve() ────────────────────────────
File base = new File("/home/user");
File child = new File(base, "documents"); // /home/user/documents
File grandchild = new File(child, "report.txt"); // /home/user/documents/report.txt
System.out.println(grandchild.getParentFile().equals(child)); // true
System.out.println(grandchild.getParent()); // /home/user/documents
// ── equals() — path string comparison, not file system identity ──────
File a = new File("/home/user/./data.txt");
File b = new File("/home/user/data.txt");
System.out.println(a.equals(b)); // false — different path strings, same file
// Use Files.isSameFile() for true identity comparison:
System.out.println(Files.isSameFile(a.toPath(), b.toPath())); // true
// Case sensitivity:
File upper = new File("/tmp/FILE.txt");
File lower = new File("/tmp/file.txt");
System.out.println(upper.equals(lower)); // false on Unix (case-sensitive)
// true on Windows (case-insensitive)
// ── toPath() — bridge to NIO.2 ────────────────────────────────────────
File legacyFile = new File("/home/user/data.txt");
Path modernPath = legacyFile.toPath(); // convert to Path
File backToFile = modernPath.toFile(); // convert back
// Prefer Path.of() for new code:
Path path = Path.of("/home/user/data.txt"); // equivalent, modernQuery Methods, Mutation Methods, and the Boolean-Return Problem
// ── Complete query method reference ──────────────────────────────────
File f = new File("/home/user/example.txt");
// Existence — all return false for any failure (existence, permissions, I/O error):
System.out.println(f.exists()); // any type of file system entry
System.out.println(f.isFile()); // regular file only
System.out.println(f.isDirectory()); // directory only
System.out.println(f.isHidden()); // starts with '.' on Unix; hidden attr on Windows
System.out.println(f.isAbsolute()); // path is absolute
// Size and timing — return 0 if doesn't exist or on error:
System.out.println(f.length()); // bytes; 0 for directories
System.out.println(f.lastModified()); // ms since epoch; 0 if doesn't exist
// Permission checks — may not reflect actual OS permissions accurately on all platforms:
System.out.println(f.canRead());
System.out.println(f.canWrite());
System.out.println(f.canExecute());
// ── Mutation methods — check return values, they're your only error signal ──
// createNewFile: creates if absent, false if exists, IOException on error:
File newFile = new File("/tmp/atomic-test.txt");
try {
boolean createdNow = newFile.createNewFile();
if (createdNow) {
System.out.println("Created new file");
} else {
System.out.println("File already existed");
}
} catch (IOException e) {
System.err.println("I/O error: " + e.getMessage());
}
// delete: returns false silently on any failure (doesn't exist, not empty, locked):
boolean deleted = newFile.delete();
if (!deleted) {
// Distinguish reason manually — File gives no clue:
if (!newFile.exists()) {
System.out.println("Was already gone");
} else {
System.out.println("Delete failed: locked? not empty? permissions?");
// NIO.2 Files.delete() would tell you which one
}
}
// mkdir vs mkdirs:
File single = new File("/tmp/single");
File nested = new File("/tmp/a/b/c/d");
boolean singleMade = single.mkdir(); // fails if /tmp doesn't exist? No, /tmp always exists
boolean nestedMade = nested.mkdirs(); // creates /tmp/a, /tmp/a/b, /tmp/a/b/c, /tmp/a/b/c/d
System.out.println("Single: " + singleMade); // true
System.out.println("Nested: " + nestedMade); // true
// renameTo: non-atomic on Windows, may fail across filesystems:
File source = new File("/tmp/original.txt");
File dest = new File("/tmp/renamed.txt");
source.createNewFile();
boolean renamed = source.renameTo(dest);
if (!renamed) {
System.err.println("renameTo failed — use Files.move() for reliable semantics");
}
// ── setters — permission and timestamp control ─────────────────────────
File controlled = new File("/tmp/controlled.txt");
controlled.createNewFile();
controlled.setReadOnly(); // make read-only
controlled.setWritable(true); // re-enable writing
controlled.setWritable(false, false); // false for owner only too
controlled.setExecutable(true, false); // executable for all users
controlled.setLastModified(System.currentTimeMillis() - 86_400_000L); // set to yesterday
// ── Static methods ────────────────────────────────────────────────────
// Filesystem roots:
for (File root : File.listRoots()) {
System.out.printf("Root: %s total=%.1fGB free=%.1fGB usable=%.1fGB%n",
root,
root.getTotalSpace() / 1e9,
root.getFreeSpace() / 1e9,
root.getUsableSpace() / 1e9 // may differ from freeSpace due to quotas
);
}
// On Unix: Root: / total=500.0GB free=350.0GB usable=340.0GB
// On Windows: Root: C: ... D: ...
// Temp directory:
System.out.println(System.getProperty("java.io.tmpdir")); // /tmp on Unix, C:Temp on Windows
// Static createTempFile:
File tmpFile = File.createTempFile("myapp-", ".tmp");
// Creates /tmp/myapp-1234567890.tmp (unique name guaranteed)
System.out.println("Temp: " + tmpFile.getAbsolutePath());
tmpFile.deleteOnExit(); // register for cleanup (best-effort)
File tmpInDir = File.createTempFile("cache-", ".dat", new File("/home/user/cache"));Directory Listing, FileFilter, FilenameFilter, and File Deficiencies
// ── list() and listFiles() — null safety is mandatory ────────────────
File dir = new File("/home/user");
// list() returns String[] — null on error or non-directory:
String[] names = dir.list();
if (names == null) throw new IOException("Cannot list: " + dir);
// Sort explicitly — order is NOT guaranteed:
Arrays.sort(names);
for (String name : names) System.out.println(name);
// listFiles() returns File[] — null on error or non-directory:
File[] files = dir.listFiles();
if (files == null) throw new IOException("Cannot list: " + dir);
Arrays.sort(files, Comparator.comparing(File::getName)); // sort by name
// ── FilenameFilter and FileFilter — lambda-friendly in Java 8+ ────────
// FilenameFilter: accepts (File parent, String filename):
FilenameFilter javaFilter = (parent, name) -> name.endsWith(".java");
String[] javaNames = dir.list(javaFilter);
// FileFilter: accepts (File pathname):
FileFilter largeFileFilter = f -> f.isFile() && f.length() > 1_000_000;
File[] largeFiles = dir.listFiles(largeFileFilter);
// Combined: list only large Java files:
File[] largeJava = dir.listFiles(
f -> f.isFile() && f.getName().endsWith(".java") && f.length() > 100_000
);
// ── Recursive listing — manually (no built-in support in File) ────────
List<File> findAllJavaFiles(File directory) {
List<File> result = new ArrayList<>();
File[] children = directory.listFiles();
if (children == null) return result; // not a directory or I/O error
for (File child : children) {
if (child.isDirectory()) {
result.addAll(findAllJavaFiles(child)); // recurse
} else if (child.getName().endsWith(".java")) {
result.add(child);
}
}
return result;
}
// NIO.2 equivalent — lazy, handles large trees, fewer lines:
List<Path> javaFiles = Files.walk(dir.toPath())
.filter(p -> p.toString().endsWith(".java"))
.collect(Collectors.toList());
// ── File deficiencies catalog — why NIO.2 was needed ─────────────────
// 1. No symbolic link detection:
File symlink = new File("/home/user/link");
symlink.isFile(); // true if symlink points to a file — cannot distinguish!
// NIO.2: Files.isSymbolicLink(path) → correct answer
// 2. Boolean returns — no exception on failure:
File file = new File("/read-only-dir/file.txt");
file.delete(); // false — is it permissions? Doesn't exist? Locked? Unknown.
// NIO.2: Files.delete(path) → throws AccessDeniedException, NoSuchFileException etc.
// 3. No atomic create-or-fail:
// createNewFile() returns false if exists — that's OK, but...
// Between exists() check and createNewFile(), another process could create it
// NIO.2: Files.createFile() throws FileAlreadyExistsException — atomic
// 4. No atomic move across potential rename race:
file.renameTo(new File("/other/path/file.txt")); // fails silently across filesystems
// NIO.2: Files.move(src, dst, ATOMIC_MOVE) → throws if not atomic
// 5. No large directory streaming:
File[] all = hugeDirWithMillions.listFiles(); // loads ALL entries into array → OOM
// NIO.2: Files.list(path) → lazy Stream<Path>
// 6. No rich attributes:
file.lastModified(); // ms since epoch — that's all
// NIO.2: Files.readAttributes(path, BasicFileAttributes.class) → full metadata
// 7. No file watching:
// Must poll using lastModified() comparisons — wastes CPU, misses events
// NIO.2: WatchService → OS-level push notifications
// ── The bridge: always convert legacy File to Path immediately ────────
void processLegacyApi(File file) {
Path path = file.toPath(); // immediately convert
// Now use all NIO.2 capabilities:
try {
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println("Size: " + attrs.size());
System.out.println("Is symlink: " + attrs.isSymbolicLink());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}