Skip to content

NIO.2 Files — Path, Files Utility Methods, walk, find, lines, and File Operations

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about NIO.2 Files. We cover key concepts, practical examples, and best practices to help you master this topic.

Java NIO.2 (introduced in Java 7) provides a modern file system API centered on the Path class and Files utility, replacing much of java.io. Unlike the java.io.File class (which had many design flaws — non-atomic operations, missing symlink support, and boolean return values instead of exceptions), NIO.2 is file-system-agnostic, supports symbolic links, and uses proper Exception Handling.

What You'll Learn

  • Path vs File, creating and resolving paths
  • Files utility: read, write, copy, move, delete
  • Walking directory trees with walk and find
  • Reading all lines and streaming lines

Why It Matters

NIO.2 is the standard file API for modern Java. java.io.File is effectively deprecated. NIO.2 is faster, more reliable, and supports features like file attributes, watch services, and directory streaming.

Real-World Use

File indexing services, log file monitoring, backup utilities, and build tools all use NIO.2. Spring Boot uses it for classpath scanning and file watching.


The Path Interface

Path path = Path.of("/home/user/docs/file.txt");
Path relative = Path.of("docs", "file.txt");

// Parts of a path
System.out.println(path.getFileName());  // file.txt
System.out.println(path.getParent());    // /home/user/docs
System.out.println(path.getRoot());      // /
System.out.println(path.getNameCount()); // 4
System.out.println(path.getName(0));     // home

// Normalize
Path normalized = Path.of("/home/./docs/../docs/file.txt").normalize();
// /home/docs/file.txt

// Resolve
Path base = Path.of("/home/user");
Path full = base.resolve("docs/file.txt");
// /home/user/docs/file.txt

// Relativize
Path from = Path.of("/a/b/c");
Path to = Path.of("/a/b/d/e");
Path relativePath = from.relativize(to); // ../d/e

Files Utility Methods

Reading and Writing Files

// Read all bytes
byte[] data = Files.readAllBytes(Path.of("file.bin"));

// Read all lines
List<String> lines = Files.readAllLines(Path.of("file.txt"), StandardCharsets.UTF_8);

// Write bytes
Files.write(Path.of("output.bin"), data);

// Write lines
Files.write(Path.of("output.txt"), lines, StandardCharsets.UTF_8);

Reading with Streams

// Stream lines (lazy — doesn't load entire file into memory)
try (Stream<String> lines = Files.lines(Path.of("large.txt"), StandardCharsets.UTF_8)) {
    lines.filter(line -> line.contains("error"))
         .forEach(System.out::println);
}

Copy, Move, Delete

// Copy
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
Files.copy(source, target, StandardCopyOption.COPY_ATTRIBUTES);

// Move
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);

// Delete
Files.delete(path);               // throws NoSuchFileException if missing
boolean deleted = Files.deleteIfExists(path); // returns false if missing

Walking Directory Trees

walk()

try (Stream<Path> stream = Files.walk(Path.of("/home/user/projects"))) {
    stream.filter(Files::isRegularFile)
          .filter(p -> p.toString().endsWith(".java"))
          .forEach(System.out::println);
}

find()

More efficient — combines walk with predicate:

try (Stream<Path> stream = Files.find(
        Path.of("/home/user"),
        Integer.MAX_VALUE,
        (path, attrs) -> attrs.isRegularFile() && path.toString().endsWith(".log"))) {
    stream.forEach(System.out::println);
}

File Attributes

Path file = Path.of("file.txt");
boolean exists = Files.exists(file);
boolean isDir = Files.isDirectory(file);
boolean isRegular = Files.isRegularFile(file);
boolean isSymlink = Files.isSymbolicLink(file);
long size = Files.size(file);
FileTime lastModified = Files.getLastModifiedTime(file);

// Basic file attributes
BasicFileAttributes attrs = Files.readAttributes(file, BasicFileAttributes.class);
attrs.creationTime();
attrs.lastAccessTime();
attrs.isSymbolicLink();
attrs.size();

Temporary Files and Directories

Path tempFile = Files.createTempFile("prefix", ".txt");
Path tempDir = Files.createTempDirectory("app-");

// Delete on exit
tempFile.toFile().deleteOnExit();

Common Mistakes

  1. Using File instead of Path. java.io.File is legacy. Always prefer java.nio.file.Path and Files for new code.
  2. Forgetting to close streams from Files.walk() and Files.lines(). These return Stream objects that hold file handles. Always use try-with-resources.
  3. Not specifying encoding. Files.readAllLines() uses UTF-8 by default (Java 18+), but older versions use the platform default. Explicit encoding is safer.
  4. Assuming Files.delete() throws if file missing. Use deleteIfExists() for optional files.
  5. Using Path.of() with null segments. Path.of("a", null, "b") throws NullPointerException. Validate inputs.

Practice Questions

1. What is the difference between Path.of("/a/b") and Path.of("/a", "b")?
Both create a path to /a/b. The varargs version joins segments with the platform separator.

2. What does Files.walk() return?
A Stream<Path> that lazily traverses the directory tree depth-first. It must be used in a try-with-resources to close the underlying directory stream.

3. How is Files.lines() different from Files.readAllLines()?
Files.lines() returns a lazy Stream<String> that reads lines on demand. Files.readAllLines() reads the entire file into a List<String> in memory.

4. What does StandardCopyOption.ATOMIC_MOVE guarantee?
The move operation is atomic at the file system level — either it completes fully or it fails, with no intermediate state. Only works within the same file system.

5. How do you create a symbolic link with NIO.2?
Files.createSymbolicLink(linkPath, targetPath). Requires appropriate OS permissions.

Challenge Question:
Write a utility FileSearcher that takes a root directory, a glob pattern (e.g., **/*.java), and a search string. It should recursively find all matching files and print lines containing the search string with file path and line number. Use Files.find() and Files.lines(). Do not load entire files into memory.

FAQ

What is the difference between `Path` and `java.io.File`?

Path is immutable, supports symbolic links, provides better error handling (exceptions instead of booleans), and integrates with NIO.2 utilities. File is legacy with a flawed API.

Can I convert between Path and File?

Yes: path.toFile() and file.toPath(). Use toFile() only when interacting with legacy APIs that require File.

What is `FileSystem` in NIO.2?

The FileSystem interface represents a file system (default OS file system, ZIP file system, or custom). FileSystems.getDefault() returns the default. FileSystem provides Path instances and directory watchers.

What is the `WatchService` API?

WatchService monitors directories for changes (create, modify, delete). It is used for file watchers, auto-reload tools, and IDE file change detection.

How do I check if two paths refer to the same file?

Use Files.isSameFile(path1, path2). This correctly handles symbolic links and case-insensitive file systems.

Mini Project

Write a program Nio2Demo.java that:

  1. Creates a temporary directory structure with nested files using Files.createDirectories()
  2. Writes content to files using Files.writeString()
  3. Walks the directory tree and prints all files with their sizes
  4. Finds all .txt files larger than 1 KB using Files.find()
  5. Copies a file with REPLACE_EXISTING and verifies they are the same with Files.isSameFile()
  6. Moves a file atomically within the same directory
  7. Deletes the temporary directory and all its contents using Files.walk() with reverse sort
  8. Demonstrates Files.lines() by searching for a specific term in all text files

What's Next

NIO.2 Files handles regular file operations efficiently. For high-performance I/O and channel-based operations, NIO provides FileChannel, ByteBuffer, and SocketChannel. Lesson 42 covers NIO Channels and Buffers for non-blocking and scatter/gather I/O.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro