Reading and Writing Files in Java with Path and Files
Introduction
Reading a configuration file, writing a report, processing a CSV export - almost every real program works with files. Modern Java does this with two classes from the
(Note - File operations throw the checked
OUTPUT of the above code (on Windows; macOS and Linux show / instead of \)
OUTPUT of the above code
Large files - reading lazily with lines()
OUTPUT of the above code
The file was never held in memory all at once - each line was read, used and discarded. The Streams API - see Java 8 Streams API TutorialClick to read tutorial on the Streams API - makes filtering and summing such data short. For full control over line-by-line reading and writing,
OUTPUT of the above code
Directories, copying, moving and deleting
OUTPUT of the above code (on Windows)
Deleting a directory tree means deleting its contents first, so the paths from
OUTPUT of the above code
Summary
In this tutorial we first created and combined
java.nio.file package: Path, which represents a location in the file system, and Files, which has static methods to read, write, copy, move and inspect files. This tutorial explains reading and writing files in Java with Path and Files. We will start with creating and working with Path objects. We will then read and write whole files in one call, and work with files line by line. Next we will process large files lazily with streams, and append to files. Lastly, we will create directories, copy, move and delete files, and handle the exceptions file operations throw.(Note - File operations throw the checked
IOException, explained in Checked vs Unchecked ExceptionsClick to read tutorial: Checked vs Unchecked Exceptions in Java. The examples declare throws IOException on main() to keep them short.)
Path - a location in the file system
A Path represents the path to a file or directory - it does not open the file, and the file does not even need to exist. Create one with Path.of() (Java 11+), giving a single string or several parts which are joined with the right separator for the operating system.
Creating and working with Path objects
//PathBasics.java
import java.nio.file.Path;
public class PathBasics {
public static void main(String[] args) {
Path report = Path.of("reports", "2026", "october.csv");
System.out.println("path : " + report);
System.out.println("file name : " + report.getFileName());
System.out.println("parent : " + report.getParent());
System.out.println("name count : " + report.getNameCount());
System.out.println("is absolute : " + report.isAbsolute());
Path base = Path.of("data");
Path resolved = base.resolve("customers.txt"); // data/customers.txt
Path sibling = resolved.resolveSibling("orders.txt"); // data/orders.txt
System.out.println("resolve : " + resolved);
System.out.println("resolveSibling: " + sibling);
System.out.println("normalize : " + Path.of("data", "..", "logs", ".", "app.log").normalize());
System.out.println("relativize : " + Path.of("data").relativize(Path.of("data", "2026", "jan.txt")));
}
}
path : reports\2026\october.csv file name : october.csv parent : reports\2026 name count : 3 is absolute : false resolve : data\customers.txt resolveSibling: data\orders.txt normalize : logs\app.log relativize : 2026\jan.txt
resolve()joins paths, andresolveSibling()replaces the last part - safer than building paths by joining strings with"/"or"\\".normalize()removes.and..parts, andrelativize()works out the path from one location to another.- A relative path such as
data/customers.txtis resolved against the program's working directory;toAbsolutePath()shows the full path.
Files.writeString(path, text)andFiles.readString(path)(Java 11+) - the whole file as oneString.Files.write(path, lines)andFiles.readAllLines(path)- aList<String>of lines.Files.write(path, bytes)andFiles.readAllBytes(path)- raw bytes, for images and other binary files.
Reading and writing whole files
//WholeFile.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class WholeFile {
public static void main(String[] args) throws IOException {
Path notes = Path.of("notes.txt");
Files.writeString(notes, "Java Fundamentals\nFile I/O with NIO\n");
String text = Files.readString(notes);
System.out.print("readString:\n" + text);
Path list = Path.of("team.txt");
Files.write(list, List.of("Tom", "Harry", "Ethan"));
List<String> lines = Files.readAllLines(list);
System.out.println("readAllLines: " + lines + " (" + lines.size() + " lines)");
byte[] bytes = Files.readAllBytes(list);
System.out.println("readAllBytes: " + bytes.length + " bytes, file size " + Files.size(list));
Files.delete(notes);
Files.delete(list);
}
}
readString: Java Fundamentals File I/O with NIO readAllLines: [Tom, Harry, Ethan] (3 lines) readAllBytes: 19 bytes, file size 19
Files.write() ends every line with the operating system's line separator - \r\n on Windows - which is why team.txt has 19 bytes: 13 for the three names, plus three 2-byte separators. On macOS and Linux it would be 16.
readString() and readAllLines() load the entire file into memory, which is a problem for files of hundreds of megabytes. Files.lines(path) instead returns a Stream<String> which reads the file one line at a time as it is consumed. The stream holds the file open, so use it in try-with-resourcesClick to read tutorial: Java try-with-resources and AutoCloseable Explained.
Processing a file line by line with Files.lines()
//LargeFile.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class LargeFile {
public static void main(String[] args) throws IOException {
Path sales = Path.of("sales.csv");
List<String> rows = new ArrayList<>(List.of("region,amount"));
for (int i = 1; i <= 100_000; i++) {
rows.add((i % 3 == 0 ? "north" : i % 3 == 1 ? "south" : "west") + "," + (i % 50));
}
Files.write(sales, rows);
System.out.println("sales.csv: " + Files.size(sales) + " bytes");
try (Stream<String> lines = Files.lines(sales)) {
long northTotal = lines.skip(1) // skip the header
.filter(line -> line.startsWith("north,"))
.mapToLong(line -> Long.parseLong(line.substring(line.indexOf(',') + 1)))
.sum();
System.out.println("north total: " + northTotal);
}
Files.delete(sales);
}
}
sales.csv: 946682 bytes north total: 816683
Files.newBufferedReader() and newBufferedWriter() are covered in Byte Streams vs Character StreamsClick to read tutorial: Java Byte Streams vs Character Streams and Buffered I/O.
Appending and open options
Writing methods accept optional StandardOpenOption values which change how the file is opened -APPEND- add to the end of the file instead of replacing it.CREATE- create the file if needed;CREATE_NEW- create it, but fail if it already exists.TRUNCATE_EXISTINGandWRITE- the defaults when no option is given (together withCREATE).
Appending to a file
//AppendLog.java
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class AppendLog {
static void log(Path file, String message) throws IOException {
Files.writeString(file, message + System.lineSeparator(),
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
}
public static void main(String[] args) throws IOException {
Path logFile = Path.of("app.log");
log(logFile, "started");
log(logFile, "user Tom logged in");
log(logFile, "stopped");
System.out.print(Files.readString(logFile));
try {
Files.writeString(logFile, "x", StandardOpenOption.CREATE_NEW);
} catch (FileAlreadyExistsException e) {
System.out.println("CREATE_NEW refused: " + e.getMessage() + " already exists");
}
Files.delete(logFile);
}
}
started user Tom logged in stopped CREATE_NEW refused: app.log already exists
Files also manages files and directories -exists(),isDirectory(),isRegularFile(),size(),getLastModifiedTime()- inspect a path.createDirectory()andcreateDirectories()- the second also creates any missing parent directories.copy()andmove()- withStandardCopyOption.REPLACE_EXISTINGto overwrite the target.delete()- fails if the file is missing;deleteIfExists()does not. A directory must be empty before it can be deleted.list()andwalk()- the entries of a directory, or of a whole directory tree.
Working with directories and files
//ManageFiles.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Comparator;
import java.util.stream.Stream;
public class ManageFiles {
public static void main(String[] args) throws IOException {
Path project = Path.of("demo-project");
Path docs = Files.createDirectories(project.resolve("docs").resolve("2026"));
Path readme = Files.writeString(project.resolve("README.txt"), "Demo project");
Files.copy(readme, docs.resolve("README-copy.txt"), StandardCopyOption.REPLACE_EXISTING);
Files.move(readme, project.resolve("ABOUT.txt"), StandardCopyOption.REPLACE_EXISTING);
System.out.println("README.txt exists: " + Files.exists(readme));
try (Stream<Path> all = Files.walk(project)) {
all.filter(p -> !p.equals(project)).forEach(p -> System.out.println((Files.isDirectory(p) ? "[dir] " : "[file] ") + project.relativize(p)));
}
try (Stream<Path> all = Files.walk(project)) { // delete the tree, deepest first
all.sorted(Comparator.reverseOrder()).forEach(p -> {
try { Files.delete(p); } catch (IOException e) { throw new RuntimeException(e); }
});
}
System.out.println("demo-project exists: " + Files.exists(project));
}
}
README.txt exists: false [file] ABOUT.txt [dir] docs [dir] docs\2026 [file] docs\2026\README-copy.txt demo-project exists: false
walk() are sorted in reverse - the deepest entries come first. More task-focused examples are in How to Copy and Move Files in JavaClick to read tutorial on copying and moving files and How to Delete a File or Directory in JavaClick to read tutorial on deleting files and directories.
Handling file exceptions
File operations throw IOException or one of its more specific subclasses, so you can react to each problem -NoSuchFileException- the file or directory does not exist (see resolving NoSuchFileExceptionClick to read how to resolve NoSuchFileException).FileAlreadyExistsException- the target already exists.DirectoryNotEmptyException- deleting a directory which still has entries.AccessDeniedException- no permission to read or write.MalformedInputException- the file is not valid in the character set used, such as non-UTF-8 text read withreadString().
Catching specific file exceptions
//FileErrors.java
import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
public class FileErrors {
public static void main(String[] args) throws IOException {
try {
Files.readString(Path.of("missing.txt"));
} catch (NoSuchFileException e) {
System.out.println("No such file: " + e.getFile());
}
Path dir = Files.createDirectories(Path.of("full-dir"));
Files.writeString(dir.resolve("a.txt"), "a");
try {
Files.delete(dir);
} catch (DirectoryNotEmptyException e) {
System.out.println("Directory not empty: " + e.getFile());
}
Files.delete(dir.resolve("a.txt"));
Files.delete(dir);
System.out.println("Cleaned up: " + !Files.exists(dir));
}
}
No such file: missing.txt Directory not empty: full-dir Cleaned up: true
Path objects with Path.of(), resolve(), normalize() and relativize(). We then read and wrote whole files with readString(), writeString(), readAllLines() and write(). Next we processed a 100,000-line file lazily with Files.lines(), and appended to a log with StandardOpenOption. We then created directories, copied, moved, listed and deleted files, and lastly caught the specific exceptions file operations throw. In the next tutorial, we will look underneath these methods at byte streams, character streams and buffered I/OClick to read tutorial: Java Byte Streams vs Character Streams and Buffered I/O.Core Java Tutorials
null and NullPointerException in Java: How to Avoid ItClick to read tutorial: null and NullPointerException in Java: How to Avoid It Java Byte Streams vs Character Streams and Buffered I/OClick to read tutorial: Java Byte Streams vs Character Streams and Buffered I/O Java Serialization and Deserialization BasicsClick to read tutorial: Java Serialization and Deserialization Basics Stack vs Heap Memory in Java and Object ReferencesClick to read tutorial: Stack vs Heap Memory in Java and Object References
null and NullPointerException in Java: How to Avoid ItClick to read tutorial: null and NullPointerException in Java: How to Avoid It Java Byte Streams vs Character Streams and Buffered I/OClick to read tutorial: Java Byte Streams vs Character Streams and Buffered I/O Java Serialization and Deserialization BasicsClick to read tutorial: Java Serialization and Deserialization Basics Stack vs Heap Memory in Java and Object ReferencesClick to read tutorial: Stack vs Heap Memory in Java and Object References