Path & Files (NIO.2) in Java
Path.of, Files.readString, readAllLines, lines, write, exists, walk.
Path.of("data", "in.txt") and expect Java to complain if the file is missing. It doesn't say a word. Is it broken, or does a Path not care?An address, not a house
A **Path is like an address written on paper: writing it down doesn't check the house exists**. Path.of("data", "in.txt") never touches the disk. The **Files** class does the real work — e.g. Files.exists(path) checks.
Path anatomy
**getFileName() is the last element, getParent() everything before it, and getNameCount()** counts the names — the root / is not a name.
Path p = Path.of("/home/ana/docs/report.txt");
p.getFileName(); // report.txt
p.getParent(); // /home/ana/docs
p.getNameCount(); // 4Taking a path apart
What does this print?
Path p = Path.of("/srv/app/logs/today.log");
System.out.println(p.getFileName());
System.out.println(
p.getParent().getFileName());
System.out.println(p.getNameCount());today.log logs 4today logs 4today.log /srv/app/logs 5
Show the answer
today.log, then the parent's last name logs, then 4 names: srv, app, logs, today.log.
resolve and normalize
**resolve(other) joins a relative path onto a base. normalize()** removes . and .. segments by stepping up for each ...
Path base = Path.of("/app/conf");
base.resolve("../logs/a.log");
// /app/conf/../logs/a.log
// normalize() → /app/logs/a.logClimbing the tree
What does this print?
Path p = Path.of("/data/in")
.resolve("./../out/x.csv")
.normalize();
System.out.println(p);/data/in/./../out/x.csv/data/out/x.csv/out/x.csv
Show the answer
/data/out/x.csv — . means "here" and disappears; .. steps up from in to data.
The Files menu
**readString(p): whole file → one String. readAllLines(p)**: → List<String>. **lines(p): a lazy** Stream<String> of lines. **walk(dir)**: a lazy Stream<Path> of a whole directory tree. Writing: **writeString / write**.
Path p = Path.of("notes", "todo.txt");
Files.writeString(p, "buy milk\n");
List<String> all = Files.readAllLines(p);Loading 20 GB into memory
readString and readAllLines load everything at once — on a 20 GB log that's an OutOfMemoryError. **Files.lines** reads lazily, line by line, so memory stays small (and it must be closed).
try (var lines = Files.lines(p)) {
long errors = lines
.filter(l -> l.contains("ERROR"))
.count();
}Files in real services
Config loaders, report generators and log processors all live on Path and Files. Choosing lines over readAllLines for big inputs, and normalize() + checks for user-supplied paths (to block ../../etc/passwd tricks), are everyday production concerns.
Key takeaways
- Path.of("data", "in.txt") just builds a path
- Files.readString / readAllLines load the whole file
- Files.lines and Files.walk return lazy Streams
- resolve() joins paths; normalize() removes . and ..
NIO.2 (Path and Files) arrived in Java 7; Path.of itself was only added in Java 11 — before that everyone wrote Paths.get(...).
Practice questions
What does this print?
Path p = Path.of("/home/ana/docs/report.txt");
System.out.println(p.getFileName());
System.out.println(p.getParent().getFileName());
System.out.println(p.getNameCount());- report.txt docs 4
- report docs 4
- report.txt /home/ana/docs 5
- report.txt ana 3
Check your answer
report.txt docs 4. getFileName is the last element; the parent's last element is docs. The root / isn't a name, so there are 4 names: home, ana, docs, report.txt.
You must count the ERROR lines in a 20 GB log file. Which approach is best?
- Files.readAllLines(p) then loop
- Files.readString(p).split("\n")
- try (var lines = Files.lines(p)) { lines.filter(…).count(); }
- new String(Files.readAllBytes(p))
Check your answer
try (var lines = Files.lines(p)) { lines.filter(…).count(); }. Files.lines reads the file lazily, line by line, so memory stays small. The other options load all 20 GB into memory first.