File-Class Power Tools: Append, Mkdirs, Inspect, Delete
What the File class can do beyond exists() and getAbsolutePath()
In a nutshell
So far you have used a File object as a path that gets handed to a Scanner or a PrintStream. That is most of what you need. But the File class can do more, and a handful of those operations show up on lab problems and APE problems often enough that they are worth a focused lesson.
This lesson covers four practical jobs that most “read a file, do work, write a file” programs eventually need:
- Append to a file instead of overwriting it (so a log keeps growing run after run).
- Create the parent directory before writing if it doesn’t exist (so
new PrintStream(new File("output/results/2026/scores.txt"))doesn’t blow up). - Inspect a file with the predicates and accessors
Fileprovides (isFile,isDirectory,length,lastModified). - Manage a file: rename, delete, list the contents of a directory.
These are not new I/O classes. They are extra methods on the same File you already know. The whole lesson is about getting more value out of one class you’ve already met.
Today in three sentences.
new PrintStream(new File(path))truncates by default, so for log-style files you wrap it around aFileOutputStreamwith the append flag set totrue. Parent directories don’t appear automatically; if you need a folder, callgetParentFile().mkdirs()first. TheFileclass also has the predicates and accessors you’d expect (isFile,length,delete,renameTo,listFiles); knowing they exist lets you write file-management code without bringing in any new library.
After this lesson, you will be able to:
- Open a file in append mode using
new PrintStream(new FileOutputStream(path, true)). - Ensure a parent directory exists with
f.getParentFile().mkdirs()before writing. - Use
isFile,isDirectory,length, andlastModifiedto inspect a file you’ve been handed. - Delete and rename files programmatically, and iterate over a directory’s contents with
listFiles().
From CSCD 110. Python uses
open(path, "a")for append,os.makedirs(dirpath, exist_ok=True)for creating folders, andos.remove/os.renamefor management. Java does the same jobs through method calls onFileand constructor flags on streams. The vocabulary is different; the operations are not.
Append mode: keeping yesterday’s writes
new PrintStream(new File("log.txt")) opens the file for writing from the start. Anything that was previously in the file is gone the moment the constructor runs. That’s right for a fresh report; it’s wrong for a log that should accumulate across runs.
Append mode says: open the file, but park the cursor at the end so new writes go after the existing contents. There is no “second PrintStream constructor” that does this directly, but the byte-stream side of the family has a FileOutputStream constructor that takes a boolean append flag, and PrintStream accepts a FileOutputStream as its source. Wrap one in the other:
PrintStream log = new PrintStream(new FileOutputStream("log.txt", true));
log.println("[" + new Date() + "] program ran");
log.close();
Read the chain inside-out. new FileOutputStream("log.txt", true) opens the file in append mode (true is the magic word). new PrintStream(...) wraps that stream so you get the familiar print / println / printf API. The true flag is the only difference between this and the truncating version you’ve been using.
Each subsequent run of the program adds another line to the file. Yesterday’s content is preserved; today’s is appended. This is the shape every log file in the world has.
If you want to be explicit that you are appending text and not binary data, the equivalent recipe with the character-stream classes from lesson 7f is new PrintWriter(new FileWriter(path, true)). The FileWriter constructor’s second argument is the same append flag. Either chain works; pick the one whose top-level class matches the rest of your program.
Common pitfall: forgetting the
trueand accidentally clobbering the log. A program that wrotenew PrintStream(new FileOutputStream("log.txt"))(no second argument, defaults tofalse) wipes the file every time you run it. The truncate happens silently when the constructor runs. If you noticed that “the log only ever has one line in it”, check the boolean.
Common pitfall: opening a
PrintStreamdirectly in append mode without theFileOutputStreamwrapper. There is nonew PrintStream(File, true)constructor. The append flag lives onFileOutputStream. Forgetting that and trying to pass a boolean toPrintStreamdirectly produces a “no such constructor” compile error. Read the error, reach for the wrapper.
Check your understanding. A program runs three times in a row. After each run, you check
log.txtand see exactly the line that the most recent run wrote (no history). Where is the bug?Reveal answer
The program is opening the file with truncating mode: probably
new PrintStream(new File("log.txt"))ornew PrintStream(new FileOutputStream("log.txt"))(defaulting tofalse). The fix is to switch to append mode:new PrintStream(new FileOutputStream("log.txt", true)). Thetrueis the entire fix.
Parent directories don’t appear by magic
Try this without preparing the directory first:
PrintStream out = new PrintStream(new File("output/2026/scores.txt"));
If the folder output/2026/ does not already exist on disk, the constructor throws FileNotFoundException with a message like (No such file or directory). The PrintStream constructor will create the file scores.txt, but it will not create the folder it lives in. That’s a separate operation.
The right move is to materialize the parent directory before opening the stream. Two File methods do most of the work:
f.getParentFile()returns the folder portion of the path as anotherFile. Foroutput/2026/scores.txt, that’soutput/2026.f.mkdirs()creates the directory chain (every missing folder along the path) and returnstrueif it created anything. Thesis important:mkdir(nos) only creates the last folder;mkdirscreates every missing parent on the way down.
The idiom:
File outFile = new File("output/2026/scores.txt");
File parent = outFile.getParentFile();
if (parent != null) {
parent.mkdirs(); // safe if the folder already exists; returns false in that case
}
PrintStream out = new PrintStream(outFile);
Two notes worth keeping. First, mkdirs is idempotent: if the directory already exists, the call does nothing and returns false. You don’t have to check whether the folder is already there; the call is safe either way. Second, getParentFile returns null when the file path has no parent component (a bare filename like "scores.txt"). Guarding with if (parent != null) is the safe form even though most lab paths will have a parent.
This three-line dance shows up in real-world batch-processing code constantly: backup scripts that organize output by year/month, save-game systems that put each user in their own subfolder, log rotators that create a new directory per day. The pattern is so common that it has a name: “ensure the directory exists.”
Common pitfall:
mkdirinstead ofmkdirs.mkdironly creates one level. Ifoutput/exists butoutput/2026/does not,parent.mkdir()succeeds. If neither exists,parent.mkdir()returnsfalsebecause it cannot createoutput/2026/whileoutput/is still missing.mkdirscreates the whole chain in one call. Use the plural unless you have a specific reason not to.
Common pitfall: calling
mkdirs()on the file path instead of its parent.outFile.mkdirs()would try to createoutput/2026/scores.txt/as a directory, which is almost certainly not what you want and will collide with the actual file you’re about to write. The directory you want to create is the parent, not the file itself.
Check your understanding. A program writes a daily report to
reports/<year>/<month>/<day>.txt. The first day it runs the program crashes withFileNotFoundException. After that, every subsequent day it works. What’s happening, and how do you fix it once and for all?Reveal answer
The first run is the day the year-and-month folder doesn’t exist yet. The constructor for
PrintStreamcannot create those parent folders on its own. After the first day fails, presumably someone created the folders manually, so subsequent days find them already in place. The permanent fix is thegetParentFile().mkdirs()dance before thePrintStreamconstructor:File f = new File(path); f.getParentFile().mkdirs(); PrintStream out = new PrintStream(f);. Now the year and month folders are created on demand on day 1, and the program never has the problem again.
Inspecting a file
Some programs need to look at a file before they read it. Maybe you only want to process files larger than a kilobyte. Maybe you want to skip directories that snuck into a list of files. Maybe you want to print the file’s modified time in a report. The File class has predicates and accessors for these jobs; you’ve already met exists() and getAbsolutePath(). Here is the rest of the useful set.
| Method | Returns | What it tells you |
|---|---|---|
f.exists() |
boolean |
Is there anything (file or directory) at this path? |
f.isFile() |
boolean |
Is this path an actual file (not a directory or special node)? |
f.isDirectory() |
boolean |
Is this path a directory? |
f.canRead() |
boolean |
Does the running process have permission to read it? |
f.canWrite() |
boolean |
Does the running process have permission to write to it? |
f.length() |
long |
Size of the file in bytes. (Returns 0 for directories.) |
f.lastModified() |
long |
Milliseconds since 1970-01-01 of the last write. |
f.getName() |
String |
The bare filename (no folders). output/2026/scores.txt returns scores.txt. |
f.getParent() |
String |
The folder portion as a string. (See also getParentFile().) |
f.getAbsolutePath() |
String |
The full path resolved against the current working directory. |
A small example that ties several of these together: print a one-line summary of every file that the program has been handed.
public static void describe(File f) {
if (!f.exists()) {
System.out.println(f.getName() + ": does not exist");
return;
}
if (f.isDirectory()) {
System.out.println(f.getName() + ": directory");
} else {
System.out.println(f.getName() + ": " + f.length() + " bytes");
}
}
Three important details. (1) exists() is checked first: every other accessor is undefined for a path that doesn’t exist on disk. (2) isDirectory() and isFile() are mutually exclusive when the path exists, but neither is true if the path is missing. (3) length() returns 0 for directories regardless of contents; if you want to know how much disk a folder is using, you have to walk its contents and sum the sizes yourself.
This kind of pre-flight inspection is the bread and butter of any program that processes a folder full of unknown files. Real example: a batch script that processes every .txt in a directory checks isFile() and getName().endsWith(".txt") before opening each one, skipping directories and non-text files cleanly.
Common pitfall: assuming
exists()impliesisFile().exists()is true for both files and directories. If you wrotenew Scanner(new File(path))immediately afterexists()and the path turned out to be a folder, the constructor throws an exception. Pairexists()withisFile()for read-side validation: the file exists and it is actually a file, not a directory.
Check your understanding. Given
File f = new File("data");(no extension), what doesf.isDirectory()return?Reveal answer
It depends on whether
dataexists on disk and what kind of thing it is. Ifdatais a folder,true. Ifdatais a file (extensions are conventional in Java, not required),false. Ifdatadoes not exist at all,false. The classFilecannot tell file from directory just from the path string; the answer is determined by what’s actually on disk at the moment the call runs.
Managing a file: delete, rename, list
The last batch of File methods are the management operations. Each is a single method call.
Delete. f.delete() removes the file (or empty directory) and returns true on success, false on failure. Failure modes include “file doesn’t exist”, “another process has it open”, and “you don’t have permission.” There is no exception; you have to check the return value if you care.
File temp = new File("scratch.tmp");
boolean deleted = temp.delete();
if (!deleted) {
System.out.println("could not delete " + temp.getAbsolutePath());
}
Rename or move. f.renameTo(File dest) renames the file at f’s path to dest’s path. If dest is in a different folder, this acts as a move. Returns true on success, false on failure. Like delete, it does not throw; it just reports.
File draft = new File("report.draft.txt");
File final_ = new File("reports/2026/report.txt");
final_.getParentFile().mkdirs();
boolean moved = draft.renameTo(final_);
List a directory. dir.listFiles() returns an array of File objects, one for each entry in the directory. The order is unspecified by the JDK (most file systems return alphabetical, but you cannot rely on it). Returns null if the path is not a directory or you don’t have permission to list it.
File dir = new File("reports/");
File[] entries = dir.listFiles();
if (entries != null) {
for (File entry : entries) {
if (entry.isFile() && entry.getName().endsWith(".txt")) {
System.out.println(entry.getName() + " (" + entry.length() + " bytes)");
}
}
}
Read that example carefully. It’s the recognition shape of a batch processor: walk every entry in a folder, filter to the ones that match a pattern, do work on each. Real programs that fit this shape include log analyzers (every .log in a folder), grading scripts (every student’s submission), photo organizers (every .jpg), and ETL pipelines (every CSV from yesterday’s drop). You will write code with this shape long after CSCD 210 ends.
A small but important quirk: listFiles() returns null (not an empty array) if the path is not a valid directory. You have to null-check before iterating, or you’ll get a NullPointerException on the for loop. The defensive form if (entries != null) { ... } is the idiomatic guard.
Common pitfall: ignoring the return value of
deleteorrenameTo. Both methods report failure through their return value, not through an exception. If you writef.delete();and don’t check the result, you have no way to know whether the file was actually deleted. The fix is to capture the boolean and act on it (or at least log a warning). For one-off scripts the consequences are mild; for production code they are not.
Check your understanding. A program tries to rename
tmp.txttoarchive/tmp.txt. The folderarchive/does not yet exist. What doestmp.renameTo(new File("archive/tmp.txt"))return, and why?Reveal answer
Returns
false.renameTocannot create parent directories; if the destination’s folder doesn’t exist, the rename fails silently and reportsfalse. The fix is the same as the write-side fix: ensure the parent exists first.new File("archive/tmp.txt").getParentFile().mkdirs(); tmp.renameTo(new File("archive/tmp.txt"));. Themkdirscall materializes the folder; the rename then has somewhere to land.
Wrap up and what’s next
Recap.
- Append mode preserves existing contents. The recipe is
new PrintStream(new FileOutputStream(path, true)); thetrueis the entire append flag. - The parent directory is not created automatically. Before opening a
PrintStreamon a nested path, callf.getParentFile().mkdirs()to ensure the folder exists. - The
Fileclass has predicates and accessors you’ll use in real programs:isFile,isDirectory,length,lastModified,getName,getParent. UseexistsplusisFiletogether when you need to validate a read target. - File management is method calls on
File:delete,renameTo,listFiles. Each reports failure through its return value, not an exception, so check the boolean. - These operations turn “I can read and write one file” into “I can manage a folder full of them,” which is the shape of most real batch programs.
What you can do now. Write programs that maintain a log file across runs, organize output into year-and-month subfolders, validate a file before opening it, delete temporary scratch files, and walk a directory listing the entries that match a pattern.
Next up: APE File I/O Walkthrough. The capstone lesson for the unit. Six APE-style File I/O exercises, dissected sentence by sentence with the four-step pattern. By the end you will have seen every major shape an APE problem can take and named the trap for each one.
Related resources
- The
java.io.FileJavadoc lists every method on the class. The recognition table here covers the ones used in CS1 and most internships. - The
java.io.FileOutputStreamJavadoc documents the append-flag constructor. Its character-stream cousinFileWriterhas the sameboolean appendparameter. - Reges & Stepp, Building Java Programs, Chapter 6 mentions a handful of
Filemethods; this lesson covers the practical superset most lab and APE problems ask about.