- Learn basic file I/O operations in Java
- Master reading from and writing to files
- Understand file system operations and directory management
Basic File Operations in Java
At some point, your program needs to talk to the outside world. Maybe you're saving user preferences, reading configuration data, or processing a CSV file someone sent you. That's where file operations come in.
Java's approach might seem a bit scattered at first—there are several classes involved—but once you see how they fit together, it makes sense.
The File class: your file system navigator
The File class is kind of like a GPS coordinate. It points to a location in your file system, but it doesn't actually touch the content inside. You use it to check if something exists, get information about files and folders, create directories, that sort of thing.
import java.io.File;
File file1 = new File("data.txt"); // relative to current directory
File file2 = new File("/home/user/documents", "report.txt"); // path + filename
File file3 = new File("C:\\Users\\user\\Desktop\\data.txt"); // absolute path (Windows)
if (file1.exists()) {
System.out.println("Found it!");
}
You can ask a File object all sorts of questions about what it's pointing to:
File file = new File("example.txt");
file.getName(); // "example.txt"
file.getAbsolutePath(); // full path on your system
file.isFile(); // true if it's a file
file.isDirectory(); // true if it's a folder
file.canRead(); // can you read it?
file.canWrite(); // can you write to it?
file.length(); // size in bytes
file.lastModified(); // timestamp of last change
Writing to files
Now let's actually put some data in a file. The simplest approach is FileWriter:
import java.io.FileWriter;
import java.io.IOException;
try (FileWriter writer = new FileWriter("output.txt")) {
writer.write("Hello, World!\n");
writer.write("This is a text file.\n");
} catch (IOException e) {
System.err.println("Error writing to file: " + e.getMessage());
}
That try-with-resources syntax (the parentheses after try) is important. It automatically closes the file when you're done, even if something goes wrong. You don't want to leave files hanging open.
But if you need formatted output—like when you're generating reports—PrintWriter is way more convenient:
import java.io.PrintWriter;
import java.io.IOException;
try (PrintWriter writer = new PrintWriter("report.txt")) {
writer.println("Student Report");
writer.println("==============");
writer.printf("Name: %s%n", "John Doe");
writer.printf("Age: %d%n", 20);
writer.printf("GPA: %.2f%n", 3.75);
} catch (IOException e) {
System.err.println("Error: " + e.getMessage());
}
Notice how printf works just like System.out.printf. You get all that nice formatting for free.
Reading from files
Reading character by character with FileReader is possible, but honestly, it's tedious:
import java.io.FileReader;
import java.io.IOException;
try (FileReader reader = new FileReader("input.txt")) {
int character;
while ((character = reader.read()) != -1) { // -1 means end of file
System.out.print((char) character);
}
} catch (IOException e) {
System.err.println("Error: " + e.getMessage());
}
Most of the time though, you want to read line by line. That's where BufferedReader comes in:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("Error: " + e.getMessage());
}
BufferedReader wraps around FileReader and adds buffering, which makes it way faster for larger files. Plus, readLine() is just easier to work with than individual characters.
Working with directories
Creating folders is straightforward:
File directory = new File("myfolder");
directory.mkdir(); // creates just this directory
File nestedDir = new File("parent/child/grandchild");
nestedDir.mkdirs(); // creates all parent directories too
The difference between mkdir() and mkdirs() trips people up. Think of mkdir() like trying to create a folder manually—it fails if the parent doesn't exist. mkdirs() is like clicking "Yes" when your OS asks "Create parent folders too?"
Listing what's inside a directory:
File directory = new File("."); // current directory
if (directory.isDirectory()) {
// Just the names
String[] names = directory.list();
for (String name : names) {
System.out.println(name);
}
// Full File objects (more useful)
File[] files = directory.listFiles();
for (File file : files) {
String type = file.isDirectory() ? "[DIR]" : "[FILE]";
System.out.printf("%s %s (%d bytes)%n", type, file.getName(), file.length());
}
}
Copying, deleting, and moving files
Copying files isn't as simple as you might hope—there's no built-in copy() method in the old File class. You have to read from one file and write to another:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
try (FileInputStream input = new FileInputStream("source.txt");
FileOutputStream output = new FileOutputStream("destination.txt")) {
byte[] buffer = new byte[1024]; // read 1KB at a time
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
System.err.println("Error copying: " + e.getMessage());
}
Yeah, it's verbose. There are newer, cleaner ways to do this with the Files class (from java.nio), but this works and helps you understand what's actually happening under the hood.
Deleting is simpler:
File file = new File("temp.txt");
if (file.delete()) {
System.out.println("Gone!");
}
File directory = new File("tempdir");
directory.delete(); // only works if the directory is empty
Important gotcha: you can't delete a directory that has files in it. You'd need to delete everything inside first, then delete the directory itself.
Renaming and moving use the same method:
File oldFile = new File("oldname.txt");
File newFile = new File("newname.txt");
oldFile.renameTo(newFile); // rename
File source = new File("file.txt");
File destination = new File("backup/file.txt");
source.renameTo(destination); // move to different directory
renameTo() does double duty—it renames if you're in the same directory, and moves if you specify a different path.
Things you need to know about errors
File operations can fail in a dozen different ways. The file doesn't exist. You don't have permission. The disk is full. That's why almost everything throws IOException.
You have two choices: catch the exception yourself, or let it bubble up to whoever called your method.
// Option 1: Handle it yourself
public static void readFile(String filename) {
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("Couldn't read '" + filename + "': " + e.getMessage());
}
}
// Option 2: Pass the problem up
public static void writeFile(String filename, String content) throws IOException {
try (PrintWriter writer = new PrintWriter(new FileWriter(filename))) {
writer.println(content);
}
// Caller has to deal with IOException
}
Neither approach is inherently better—it depends on what makes sense for your program. If you can recover from the error, handle it. If not, let it propagate.
Common mistakes to avoid
Not closing files properly
Here's the wrong way:
// Don't do this
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
// use reader
} finally {
if (reader != null) {
try {
reader.close(); // ugh, nested try-catch
} catch (IOException e) {
// now what?
}
}
}
Here's the right way:
// Do this instead
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
// use reader
} // closes automatically, even if there's an exception
Try-with-resources was added to Java specifically to solve this problem. Use it.
Not checking if you can actually access the file
Before you try to read or write, it's smart to check:
File file = new File(filename);
if (!file.exists()) {
System.out.println("File doesn't exist!");
return;
}
if (!file.canRead()) {
System.out.println("Can't read this file!");
return;
}
// Now safe to proceed
This saves you from cryptic error messages later.
Hardcoding path separators
Don't write paths like this: "data/files/input.txt" or "data\\files\\input.txt"
Windows uses backslashes, Unix-like systems use forward slashes. Use File.separator to be safe:
String path = "data" + File.separator + "files" + File.separator + "input.txt";
File file = new File(path);
Or even better, use Paths.get() which handles this automatically:
import java.nio.file.Path;
import java.nio.file.Paths;
Path path = Paths.get("data", "files", "input.txt");
File file = path.toFile();
Putting it together
File operations in Java can feel scattered because there are so many classes involved. But here's the mental model: File is for navigating and managing the file system itself. Then you have readers and writers for actually dealing with content—FileReader/FileWriter for basic operations, BufferedReader/PrintWriter when you need more power.
Always use try-with-resources. Always check if files exist and if you have permission before trying to access them. And remember that file operations fail often—disk issues, permission problems, files being locked by other programs—so exception handling isn't optional, it's part of the design.
Once you get comfortable with these basics, you'll probably want to explore the newer java.nio.file package, which has cleaner APIs for most of this stuff. But understanding the fundamentals here will make that transition much easier.
