Almost every application reads configuration, writes reports or processes data files.
Modern file handling uses NIO.2 (java.nio.file). A Path represents a location: Path.of("data", "input.csv") builds it portably, and resolve, getParent, getFileName and normalize manipulate it without touching the disk.
The Files utility class does the actual I/O:
- Small files:
Files.readString(path),Files.readAllLines(path),Files.writeString(path, text)andFiles.write(path, lines). - Large files:
Files.lines(path)returns a lazyStream<String>;Files.newBufferedReaderandnewBufferedWritergive classic readers and writers. - Management:
exists,createDirectories,copy,move(with options likeREPLACE_EXISTING,ATOMIC_MOVE),delete,deleteIfExists,size. - Directories:
Files.list(dir)for one level,Files.walk(dir)recursively,Files.findwith a filter.
Streams returned by lines, list and walk hold open file handles, so always use them in try-with-resources. Forgetting this leaks descriptors and eventually fails with "Too many open files".
Text needs a charset. Since Java 18 the default is UTF-8 everywhere (JEP 400), and the Files methods use UTF-8 unless told otherwise. Pass a charset explicitly when reading files from other systems, for example StandardCharsets.ISO_8859_1.
Write options control behaviour: StandardOpenOption.APPEND, CREATE, TRUNCATE_EXISTING. To avoid readers seeing half-written files, write to a temporary file in the same directory and move it into place with ATOMIC_MOVE.
You'll still meet java.io.File and InputStream/OutputStream in older APIs; file.toPath() and path.toFile() bridge the two worlds.
Example
import java.io.IOException;
import java.nio.file.*;
public class FilesDemo {
public static void main(String[] args) throws IOException {
Path dir = Files.createTempDirectory("demo");
Path log = dir.resolve("app.log");
Files.writeString(log, """
INFO started
ERROR disk full
INFO retry
ERROR timeout
""");
try (var lines = Files.lines(log)) { // closes the file
System.out.println(lines.filter(l -> l.startsWith("ERROR")).count()); // 2
}
Path tmp = dir.resolve("report.tmp"); // write, then move into place
Files.writeString(tmp, "errors=2\n");
Files.move(tmp, dir.resolve("report.txt"), StandardCopyOption.ATOMIC_MOVE);
try (var files = Files.list(dir)) {
files.map(p -> p.getFileName().toString()).sorted().forEach(System.out::println);
} // app.log, report.txt
}
}Remember
- Use
Path.ofand theFilesutility methods for file I/O - Close
Files.lines,listandwalkstreams with try-with-resources - UTF-8 is the default since Java 18; specify charsets for foreign files
Watch out for
- Using
Files.readAllLineson multi-gigabyte files and running out of memory — stream them instead.