W3docs

Java File Class

Represent file system paths in Java with the legacy java.io.File class — exists, isFile, isDirectory, listFiles.

Java File Class

java.io.File is the original "this string is a path" type from Java 1.0. The class itself does no I/O — it doesn't open, read, or write data — it just names a location in the file system and offers a handful of methods to ask the OS about that location and to perform single-shot operations on it (exists, isDirectory, delete, renameTo, listFiles).

java.nio.file.Path (Java 7) is the modern replacement and is what new code should use, but you'll meet File in every codebase older than ~2012, and many older APIs still accept and return it. This chapter covers what it does, where the seams are, and how it bridges to Path.

Construction

A File wraps a path string. Four constructors cover the common cases:

File a = new File("data/users.txt");                 // relative to the JVM's working directory
File b = new File("/var/log/app.log");                // absolute
File c = new File("/tmp", "session.txt");             // parent + child
File d = new File(new File("/tmp"), "session.txt");  // parent File + child

The constructor does no validation — passing a nonsense path constructs a File happily; only when you call exists(), delete(), etc. does the OS get involved.

Use the two-arg constructor for "parent + name" instead of string concatenation. It picks the right separator (/ on Unix, \ on Windows) and avoids the bug where the parent path may or may not end in a separator:

File good = new File(parentDir, "data.txt");         // separator handled for you
File bad  = new File(parentDir + "/data.txt");        // brittle: depends on parentDir's exact string

Querying the file system

File exposes a wide set of boolean- and long-returning queries. The common ones:

File f = new File("data/users.txt");
f.exists();              // does the path point to anything?
f.isFile();              // is it a regular file?
f.isDirectory();         // is it a directory?
f.isHidden();            // hidden by OS convention (leading dot on Unix, hidden attr on Windows)
f.length();              // size in bytes (0 for a directory)
f.lastModified();        // epoch millis; 0 if it doesn't exist or can't be queried
f.canRead();             // permission check from the JVM's point of view
f.canWrite();
f.canExecute();

These calls each hit the OS. They're cheap individually but not free — calling exists() then isDirectory() then length() is three syscalls. If you need several attributes of one file, Files.readAttributes(path, BasicFileAttributes.class) (next part) is one syscall instead.

Path views

File gives you several ways to look at the same underlying string:

File f = new File("data/../data/users.txt");
f.getName();              // "users.txt"        — last component
f.getParent();            // "data/../data"     — String, or null at the root
f.getParentFile();        // File for the parent, or null
f.getPath();              // "data/../data/users.txt" — what you constructed
f.getAbsolutePath();      // resolved against working dir, NOT canonicalised
f.getCanonicalPath();     // resolved, normalised, symlinks followed — can throw IOException

getAbsolutePath and getCanonicalPath are the most confusable pair in the class:

  • getAbsolutePath — prepends the JVM's current working directory if the path is relative. Returns the string with .. segments still in it.
  • getCanonicalPath — same as absolute, then resolves .. and ., then follows symbolic links. May hit the disk and throw IOException.

For security-sensitive checks (is this user-supplied path inside the allowed directory?), getCanonicalPath is the only safe one — otherwise a relative path like safe-dir/../../../etc/passwd slips past a startsWith("safe-dir") check.

Listing a directory

Four flavours, two pairs:

File dir = new File("/tmp");

String[]  names    = dir.list();                              // child names, no metadata
File[]    children = dir.listFiles();                          // child File objects

String[]  txt      = dir.list((d, name) -> name.endsWith(".txt"));         // FilenameFilter
File[]    files    = dir.listFiles(File::isFile);                            // FileFilter

Both FilenameFilter and FileFilter are single-method functional interfaces (Part 12 vocabulary), so a lambda or method reference works directly. The difference: FilenameFilter receives the parent directory and the bare name; FileFilter receives the constructed child File. Use FileFilter if you need to call isDirectory() or length() to decide; use FilenameFilter if name matching is enough.

All four methods return null if the path isn't a directory — they don't throw. That's a textbook NPE source:

for (File child : dir.listFiles()) { ... }                   // NPE if dir is not a directory!
File[] children = dir.listFiles();
if (children != null) for (File c : children) { ... }         // correct

The modern Files.list(path) returns an empty Stream<Path> for a missing directory or throws a clear NotDirectoryException. The File API just hands back null and lets you crash.

Creating, deleting, renaming

File exposes a few mutation methods:

f.createNewFile();        // creates an empty file; returns boolean; throws IOException on real failure
f.mkdir();                // creates this directory; parent must exist
f.mkdirs();               // creates this directory and any missing parents
f.delete();               // deletes this file or empty directory; returns boolean
f.renameTo(other);        // OS-specific behaviour; returns boolean

The recurring theme — boolean return values that don't tell you why — is the single biggest reason Files exists. f.delete() returns false if the file didn't exist, if you lacked permission, if it was a non-empty directory, or if another process held it open on Windows. You can't tell which from the return value. The corresponding Files.delete(path) throws a specific exception (NoSuchFileException, AccessDeniedException, DirectoryNotEmptyException) and is the API you want for real error handling.

renameTo is the worst offender: it can fail without raising any exception, and the failure modes (across-volume rename, target exists, permission, lock) depend on the OS. Files.move(src, dst, REPLACE_EXISTING) is the modern replacement and tells you what went wrong.

Bridging to Path

Every File knows its Path and vice versa:

File f = new File("data/users.txt");
Path p = f.toPath();              // bridge to java.nio.file
File g = p.toFile();              // bridge back

The two interoperate cheaply. When you're stuck with a legacy API that returns File, the right move is usually f.toPath() then call Files.* on it. New code should start from Path.of(...) and only convert to File at the call site of a legacy method.

A worked example: building a tree and walking it with File

The program below creates a small directory tree under the system temp directory, populates it with a few files, then queries each entry with File. It demonstrates the FilenameFilter and FileFilter lambdas, the null-return trap, the canonical-path resolution, and the missing-error-info problem with delete(). Every artefact is cleaned up with deleteOnExit.

java— editable, runs on the server

What to take from the run:

  • a.getCanonicalPath() printed a normalised absolute path with no .. segments. getAbsolutePath() does not normalise — for a security check, canonical is the version you compare against an allowed-prefix.
  • The FilenameFilter form (d, name) -> name.endsWith(".txt") is a two-argument lambda; File::isFile is a method reference for the one-argument FileFilter. Both are functional interfaces, the same vocabulary as Part 12 — File has been "lambda-ready" since long before lambdas existed.
  • notDir.listFiles() returned null because data.csv isn't a directory. The for loop would have NPE'd if we'd skipped the null check. Files.list(path) raises a clear exception for the same case.
  • ghost.delete(), a.delete(), and sub.delete() all returned a boolean. The first two are easy to read; the third returned false because the directory wasn't empty, and the API gave us nothing to distinguish "wasn't empty" from "didn't have permission." That's the gap Files.delete(path) closes.
  • root.toPath() is the bridge into java.nio.file. Once you have a Path, the rest of Part 13 applies — Files.readString, Files.lines, Files.walk, all the static helpers.

What's next

The next chapter, Creating Files in Java, covers the three ways to make a new file or directory — the legacy File.createNewFile and mkdir(s), plus the modern Files.createFile, Files.createDirectory, and Files.createDirectories — and which one to pick for which job.

Practice

Practice

`dir.listFiles()` on a `File` that points to a regular file (not a directory) returns…