☕ Java

Files Class

java.nio.file.Files is a utility class introduced in Java 7 containing over 70 static methods that perform all file system operations on Path objects: reading and writing file content, querying and setting file metadata, creating and deleting files and directories, copying and moving, directory listing and recursive traversal, checking permissions and existence, managing symbolic links, and obtaining channels and streams. Files is the NIO.2 replacement for the operations previously handled by java.io.File's mutation methods (which return boolean on failure) and for the InputStream/OutputStream factory pattern used with java.io streams. Every Files method throws IOException (or a subclass like NoSuchFileException, FileAlreadyExistsException, DirectoryNotEmptyException, or AccessDeniedException) rather than returning boolean, making error handling explicit and unambiguous. This entry covers every major category of Files method in depth, the complete set of StandardOpenOption and StandardCopyOption values, atomic operations and their filesystem-level semantics, bulk read/write convenience methods, the directory traversal API including Files.walk, Files.find, and Files.walkFileTree, permission and attribute manipulation, and the interplay between Files methods and the underlying OS system calls.

Content Reading and Writing — All Forms

Files provides eight methods for reading file content and eight for writing, covering all combinations of byte array vs string vs line collection vs stream, and covering all common use cases from small config files to large streaming reads. For reading: Files.readAllBytes(Path) reads the entire file into a byte[] — appropriate for binary files or small text files that will be re-encoded. Files.readString(Path) and Files.readString(Path, Charset) read the entire file as a String — the clearest option for small text files. Files.readAllLines(Path) and Files.readAllLines(Path, Charset) read all lines as List<String>, loading the entire file into memory. Files.lines(Path) and Files.lines(Path, Charset) return a lazy Stream<String> that reads lines on demand — the correct choice for large text files because it reads only as many lines as the stream consumes. Files.newInputStream(Path, OpenOption...) returns an InputStream for streaming byte access — suitable for large binary files or when the caller needs an InputStream for compatibility with other APIs. Files.newBufferedReader(Path) and Files.newBufferedReader(Path, Charset) return a BufferedReader for line-at-a-time text reading. For writing: Files.write(Path, byte[], OpenOption...) writes a byte array — for binary or pre-encoded content. Files.writeString(Path, CharSequence, OpenOption...) and Files.writeString(Path, CharSequence, Charset, OpenOption...) write a String — the cleanest option for text output. Files.write(Path, Iterable<? extends CharSequence>, Charset, OpenOption...) writes lines, appending the system line separator after each. Files.newOutputStream(Path, OpenOption...) returns an OutputStream for streaming writes. Files.newBufferedWriter(Path) and Files.newBufferedWriter(Path, Charset, OpenOption...) return a BufferedWriter. The OpenOption parameters control file creation and opening behavior. The most important values from StandardOpenOption: CREATE creates the file if it doesn't exist but does not fail if it does (use with WRITE or APPEND). CREATE_NEW creates the file and throws FileAlreadyExistsException if it already exists — the only atomic exclusive-create option. TRUNCATE_EXISTING truncates the file to zero before writing (combined with CREATE, this is the overwrite-or-create behavior). APPEND positions writes at the end of the file. READ opens for reading. WRITE opens for writing. SYNC makes each write synchronously flush to hardware (O_SYNC). DSYNC synchronizes data but not metadata. The convenience write methods (readAllBytes, writeString, etc.) use CREATE and TRUNCATE_EXISTING by default.
Java
// ── Reading: all forms ───────────────────────────────────────────────
Path p = Path.of("data.txt");

// Entire file as byte array — binary or small files:
byte[] bytes = Files.readAllBytes(p);
System.out.println("Bytes: " + bytes.length);

// Entire file as String — small text files (Java 11+):
String text = Files.readString(p, StandardCharsets.UTF_8);
System.out.println("Chars: " + text.length());

// All lines as List<String> — loads entire file into memory:
List<String> lines = Files.readAllLines(p, StandardCharsets.UTF_8);
System.out.println("Lines: " + lines.size());

// Lazy stream of lines — for large files (MUST close):
try (Stream<String> stream = Files.lines(p, StandardCharsets.UTF_8)) {
    long errorCount = stream.filter(l -> l.contains("ERROR")).count();
    System.out.println("Errors: " + errorCount);
}   // stream.close() releases the underlying file handle

// InputStream for binary streaming:
try (InputStream is = Files.newInputStream(p, StandardOpenOption.READ)) {
    byte[] buf = new byte[4096];
    int n;
    while ((n = is.read(buf)) != -1) processBytes(buf, n);
}

// BufferedReader for text streaming:
try (BufferedReader br = Files.newBufferedReader(p, StandardCharsets.UTF_8)) {
    String line;
    while ((line = br.readLine()) != null) processLine(line);
}

// ── Writing: all forms ────────────────────────────────────────────────
Path out = Path.of("output.txt");

// Byte array — binary output or pre-encoded text:
Files.write(out, "Hello
".getBytes(StandardCharsets.UTF_8));

// String — clean text output (Java 11+):
Files.writeString(out, "Hello, World!
", StandardCharsets.UTF_8);

// Lines — each element followed by system line separator:
Files.write(out, List.of("Line 1", "Line 2", "Line 3"), StandardCharsets.UTF_8);

// OutputStream for large/streaming binary:
try (OutputStream os = Files.newOutputStream(out,
        StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
    byte[] buf = new byte[65536];
    while (hasMoreData()) { os.write(buf, 0, fillBuffer(buf)); }
}

// BufferedWriter for large/streaming text:
try (BufferedWriter bw = Files.newBufferedWriter(out, StandardCharsets.UTF_8)) {
    for (Record r : getRecords()) {
        bw.write(r.toCsv());
        bw.newLine();
    }
}

// ── OpenOptions — controlling creation and truncation ────────────────
// Default (create or overwrite):
Files.writeString(out, "content");   // same as CREATE + TRUNCATE_EXISTING + WRITE

// Fail if exists — atomic exclusive create:
try {
    Files.writeString(out, "content",
        StandardOpenOption.CREATE_NEW,   // throws FileAlreadyExistsException if exists
        StandardOpenOption.WRITE);
} catch (FileAlreadyExistsException e) {
    System.out.println("File already exists: " + e.getFile());
}

// Append to existing:
Files.writeString(out, "new line
",
    StandardOpenOption.CREATE, StandardOpenOption.APPEND);

// Sync to hardware on every write (expensive but durable):
try (OutputStream os = Files.newOutputStream(out,
        StandardOpenOption.CREATE, StandardOpenOption.WRITE,
        StandardOpenOption.SYNC)) {   // O_SYNC
    os.write(criticalData);   // each write flushes to hardware
}

Existence, Metadata, Create, Delete, Copy, and Move

Files.exists(Path, LinkOption...) returns true if the path refers to an existing file system entry. By default it follows symbolic links — if the path is a symlink, it returns true only if the symlink's target exists. With LinkOption.NOFOLLOW_LINKS, it tests whether the path itself (which may be a broken symlink) exists. Files.notExists() is the negation but with an important subtlety: it returns true only when the path definitely does not exist; it returns false (not true) when an IOException occurs during the check, so it is not equivalent to !Files.exists(). Files.isRegularFile(), Files.isDirectory(), Files.isSymbolicLink(), Files.isReadable(), Files.isWritable(), Files.isExecutable() test specific properties and all return false if the path doesn't exist or if an IOException occurs during the check. Files.createFile(Path) atomically creates a new empty file and throws FileAlreadyExistsException if the file already exists. This atomicity is the critical advantage over File.createNewFile(): the existence check and the creation happen in a single OS operation (open() with O_CREAT|O_EXCL), eliminating the TOCTOU (time-of-check to time-of-use) race condition. Files.createDirectory(Path) creates a single directory level, throwing FileAlreadyExistsException if it exists. Files.createDirectories(Path) creates all missing directories in the path and does not throw if the target already exists — it is idempotent. Files.delete(Path) throws NoSuchFileException if the path doesn't exist, DirectoryNotEmptyException if the path is a non-empty directory, and AccessDeniedException if the process lacks permission. This explicit exception taxonomy makes error handling precise. Files.deleteIfExists(Path) returns true if deleted, false if not found, and throws on other errors — the idempotent delete. Files.copy(Path source, Path target, CopyOption... options) copies a file or directory entry (not recursively). For directories, only the directory entry is copied — not its contents. StandardCopyOption.REPLACE_EXISTING replaces the target if it exists. StandardCopyOption.COPY_ATTRIBUTES copies file attributes (modification time, permissions). StandardCopyOption.ATOMIC_MOVE is not applicable to copy (only to move). Files.copy also has overloads for copying between streams and paths: Files.copy(InputStream, Path) reads from an InputStream and writes to a file; Files.copy(Path, OutputStream) reads from a file and writes to an OutputStream. Files.move(Path source, Path target, CopyOption... options) moves or renames a file. With StandardCopyOption.ATOMIC_MOVE, the operation is guaranteed to be atomic at the kernel level when source and target are on the same filesystem. If ATOMIC_MOVE is requested but cannot be performed (cross-filesystem move), AtomicMoveNotSupportedException is thrown. REPLACE_EXISTING replaces the target if it exists (without this option, a FileAlreadyExistsException is thrown if the target exists). Files.move for cross-filesystem operations internally copies then deletes — this is not atomic and may leave both source and target if interrupted.
Java
// ── Existence and type tests ──────────────────────────────────────────
Path p = Path.of("/home/user/file.txt");

System.out.println(Files.exists(p));                    // follows symlinks
System.out.println(Files.exists(p, LinkOption.NOFOLLOW_LINKS));  // tests symlink itself
System.out.println(Files.notExists(p));                 // false if IOException (not !exists!)
System.out.println(Files.isRegularFile(p));
System.out.println(Files.isDirectory(p));
System.out.println(Files.isSymbolicLink(p));
System.out.println(Files.isReadable(p));
System.out.println(Files.isWritable(p));
System.out.println(Files.isExecutable(p));
System.out.println(Files.isHidden(p));
System.out.println(Files.size(p));                      // throws NoSuchFileException if missing
System.out.println(Files.getLastModifiedTime(p));       // FileTime
Files.setLastModifiedTime(p, FileTime.from(Instant.now()));

// ── Create operations ─────────────────────────────────────────────────
// Atomic exclusive create (no TOCTOU race):
try {
    Files.createFile(Path.of("/tmp/lock.flag"));   // atomic: create OR exception
} catch (FileAlreadyExistsException e) {
    System.out.println("Lock already held");
}

// Create directories:
Files.createDirectory(Path.of("/tmp/single"));            // one level only
Files.createDirectories(Path.of("/tmp/a/b/c/d"));        // all missing levels, idempotent

// Temp files:
Path tmpFile = Files.createTempFile("prefix-", ".tmp");         // in system temp dir
Path tmpFileInDir = Files.createTempFile(Path.of("/work"), "tmp-", null);
Path tmpDir  = Files.createTempDirectory("myapp-");
try {
    doWork(tmpFile);
} finally {
    Files.deleteIfExists(tmpFile);
}

// ── Delete operations ─────────────────────────────────────────────────
try {
    Files.delete(Path.of("/tmp/file.txt"));   // precise exceptions:
} catch (NoSuchFileException e) {
    System.err.println("Didn't exist: " + e.getFile());
} catch (DirectoryNotEmptyException e) {
    System.err.println("Directory not empty: " + e.getFile());
} catch (AccessDeniedException e) {
    System.err.println("Permission denied: " + e.getFile());
}

boolean deleted = Files.deleteIfExists(Path.of("/tmp/maybe.txt"));  // idempotent
System.out.println("Was deleted: " + deleted);   // false if didn't exist

// ── Copy operations ───────────────────────────────────────────────────
Path src  = Path.of("/source/data.bin");
Path dst  = Path.of("/dest/data.bin");

Files.copy(src, dst);   // throws FileAlreadyExistsException if dst exists
Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);   // overwrite
Files.copy(src, dst,
    StandardCopyOption.REPLACE_EXISTING,
    StandardCopyOption.COPY_ATTRIBUTES);   // also copy timestamps, permissions

// Copy from InputStream to file (download pattern):
try (InputStream download = new URL("https://example.com/file.zip").openStream()) {
    Files.copy(download, Path.of("/tmp/file.zip"), StandardCopyOption.REPLACE_EXISTING);
}

// Copy from file to OutputStream:
try (OutputStream out = response.getOutputStream()) {
    Files.copy(Path.of("/static/index.html"), out);
}

// ── Move and atomic rename ────────────────────────────────────────────
Path src2 = Path.of("/tmp/new-data.bin");
Path dst2 = Path.of("/var/data/data.bin");

// Simple rename (same filesystem — atomic on POSIX):
Files.move(src2, dst2, StandardCopyOption.REPLACE_EXISTING);

// Explicit atomic move (throws AtomicMoveNotSupportedException if cross-filesystem):
try {
    Files.move(src2, dst2,
        StandardCopyOption.ATOMIC_MOVE,
        StandardCopyOption.REPLACE_EXISTING);
} catch (AtomicMoveNotSupportedException e) {
    // Fall back to copy-then-delete (non-atomic):
    Files.copy(src2, dst2, StandardCopyOption.REPLACE_EXISTING);
    Files.delete(src2);
}

// Safe update pattern: write to temp, atomic rename to target:
Path target = Path.of("/config/settings.json");
Path temp   = Files.createTempFile(target.getParent(), ".tmp-", null);
try {
    Files.writeString(temp, newConfigJson, StandardCharsets.UTF_8);
    Files.move(temp, target, StandardCopyOption.ATOMIC_MOVE,
        StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) {
    Files.deleteIfExists(temp);
    throw e;
}

Directory Traversal, Attributes, Symlinks, and Permissions

Files.list(Path dir) returns a lazy Stream<Path> of the immediate children of a directory. It is the NIO.2 replacement for File.listFiles(), with the critical advantage that it is lazy — it reads directory entries one at a time from the OS rather than loading the entire directory listing into an array. This matters for directories with millions of entries. The stream must be closed; use try-with-resources. Files.list() does not recursively descend into subdirectories. Files.walk(Path start, int maxDepth, FileVisitOption... options) returns a lazy Stream<Path> in depth-first order covering all entries in the subtree. Without maxDepth (or with Integer.MAX_VALUE), it walks the entire tree. Files.walk() does not follow symlinks by default; FileVisitOption.FOLLOW_LINKS enables following, with cycle detection via FileSystemLoopException. Files.find(Path start, int maxDepth, BiPredicate<Path, BasicFileAttributes> matcher, FileVisitOption...) combines walking with attribute-based filtering in one efficient pass — the attributes are read as part of the directory scan and passed to the predicate, avoiding an extra readAttributes() call per entry. Files.walkFileTree(Path start, FileVisitor<? super Path> visitor) provides the full visitor pattern for precise traversal control. SimpleFileVisitor provides default implementations that continue traversal; override only the methods you need. The FileVisitResult enum controls traversal: CONTINUE, SKIP_SUBTREE (skip this directory's subtree), SKIP_SIBLINGS (skip remaining entries in the current directory), TERMINATE (stop the entire walk). Files.readAttributes(Path, Class<A>, LinkOption...) returns a rich attribute view. BasicFileAttributes covers size, times, and type. PosixFileAttributes (Unix) adds owner, group, and permission bits as PosixFilePermission enums. DosFileAttributes (Windows) adds readonly, hidden, system, archive flags. The attribute reading is done in a single system call (stat() on POSIX) — more efficient than multiple individual Files.isDirectory(), Files.size() calls. Files.createSymbolicLink(Path link, Path target) creates a symlink. Files.readSymbolicLink(Path link) reads where a symlink points (throws NotLinkException if not a symlink). Files.getPosixFilePermissions() and Files.setPosixFilePermissions() read and write Unix permissions as a Set<PosixFilePermission>. Files.getOwner() and Files.setOwner() manage file ownership. Files.getFileStore(Path) returns the FileStore (disk partition) containing the path, with methods for total/usable/unallocated space.
Java
// ── Files.list — lazy directory listing ──────────────────────────────
Path dir = Path.of("/home/user");

// Single directory, lazy:
try (Stream<Path> entries = Files.list(dir)) {
    entries.filter(Files::isRegularFile)
           .sorted()
           .forEach(System.out::println);
}

// Count entries without loading all into memory:
try (Stream<Path> entries = Files.list(dir)) {
    long count = entries.count();
    System.out.println("Entries: " + count);
}

// ── Files.walk — recursive lazy traversal ────────────────────────────
Path project = Path.of("/home/user/project");

// All Java files, any depth:
try (Stream<Path> walk = Files.walk(project)) {
    walk.filter(p -> p.toString().endsWith(".java"))
        .sorted()
        .forEach(System.out::println);
}

// Limited depth — direct children only (same as list):
try (Stream<Path> walk = Files.walk(project, 1)) {
    walk.skip(1)   // skip the root itself
        .forEach(System.out::println);
}

// Total size of directory tree:
try (Stream<Path> walk = Files.walk(project)) {
    long totalSize = walk
        .filter(Files::isRegularFile)
        .mapToLong(p -> { try { return Files.size(p); }
                          catch (IOException e) { return 0L; } })
        .sum();
    System.out.printf("Total: %.2f MB%n", totalSize / 1e6);
}

// ── Files.find — walk + attribute filter in one pass ──────────────────
FileTime oneWeekAgo = FileTime.from(Instant.now().minus(7, ChronoUnit.DAYS));

// Find large files modified in the last week:
try (Stream<Path> found = Files.find(project, Integer.MAX_VALUE,
        (path, attrs) ->
            attrs.isRegularFile() &&
            attrs.size() > 1_000_000 &&
            attrs.lastModifiedTime().compareTo(oneWeekAgo) > 0)) {
    found.forEach(p -> {
        try { System.out.printf("%s (%,d bytes)%n", p, Files.size(p)); }
        catch (IOException e) {}
    });
}

// ── walkFileTree — delete directory tree ─────────────────────────────
void deleteTree(Path root) throws IOException {
    Files.walkFileTree(root, new SimpleFileVisitor<>() {
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                throws IOException {
            Files.delete(file);
            return FileVisitResult.CONTINUE;
        }
        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc)
                throws IOException {
            if (exc != null) throw exc;
            Files.delete(dir);
            return FileVisitResult.CONTINUE;
        }
    });
}

// ── Rich attribute reading ─────────────────────────────────────────────
Path file = Path.of("/home/user/data.txt");

// Basic attributes — one stat() call:
BasicFileAttributes basic = Files.readAttributes(file, BasicFileAttributes.class);
System.out.println("Size:       " + basic.size());
System.out.println("Created:    " + basic.creationTime());
System.out.println("Modified:   " + basic.lastModifiedTime());
System.out.println("Accessed:   " + basic.lastAccessTime());
System.out.println("Is file:    " + basic.isRegularFile());
System.out.println("Is dir:     " + basic.isDirectory());
System.out.println("Is symlink: " + basic.isSymbolicLink());
System.out.println("File key:   " + basic.fileKey());  // inode number on Unix

// POSIX attributes (Unix only):
PosixFileAttributes posix = Files.readAttributes(file, PosixFileAttributes.class);
System.out.println("Owner: " + posix.owner().getName());
System.out.println("Group: " + posix.group().getName());
Set<PosixFilePermission> perms = posix.permissions();
System.out.println("Perms: " + PosixFilePermissions.toString(perms));  // e.g., "rwxr-x---"

// Setting permissions:
Files.setPosixFilePermissions(file,
    PosixFilePermissions.fromString("rw-r--r--"));

// ── Symbolic links ─────────────────────────────────────────────────────
Path link   = Path.of("/home/user/link");
Path target = Path.of("/data/actual-file.txt");

Files.createSymbolicLink(link, target);          // create symlink
Path where = Files.readSymbolicLink(link);        // where does it point?
System.out.println("Link → " + where);           // /data/actual-file.txt

// Check without following:
System.out.println(Files.isSymbolicLink(link));  // true
System.out.println(Files.exists(link, LinkOption.NOFOLLOW_LINKS));  // true (link exists)
System.out.println(Files.exists(link));          // true only if target exists

// ── FileStore — disk space information ───────────────────────────────
FileStore store = Files.getFileStore(file);
System.out.printf("Drive: %s  Total: %.1fGB  Free: %.1fGB%n",
    store.name(),
    store.getTotalSpace()   / 1e9,
    store.getUsableSpace()  / 1e9);

Related Topics in Java NIO

NIO Overview
Java NIO (New I/O), introduced in Java 1.4 as the java.nio package, is a collection of APIs that complement and in many cases supersede the original java.io stream model. NIO introduces three foundational abstractions absent from java.io: buffers (typed, fixed-capacity containers for data that support direct memory allocation outside the Java heap), channels (bidirectional, closeable connections to I/O entities that read and write buffers rather than individual bytes), and selectors (multiplexers that let a single thread monitor multiple channels for readiness, enabling non-blocking I/O at scale). Java 7 extended NIO with the java.nio.file package, known as NIO.2 or JSR-203, which provides the Path interface, the Files utility class, the FileSystem abstraction, WatchService for file system event monitoring, and the AsynchronousFileChannel API. Together, NIO and NIO.2 enable four I/O models unavailable in java.io: non-blocking I/O (channel reads and writes return immediately if data is unavailable), asynchronous I/O (I/O operations are submitted and a callback or Future signals completion), memory-mapped I/O (a file is mapped directly into the JVM's virtual address space for zero-copy access), and scatter/gather I/O (reading into or writing from multiple buffers in a single system call). This entry covers the conceptual architecture of NIO, the relationship between buffers, channels, and selectors, the four I/O models, the NIO.2 additions and how they relate to the core NIO abstractions, the difference between blocking and non-blocking channels, and which NIO API to use for which problem.
Path
Path is the central interface in java.nio.file, introduced in Java 7 as part of NIO.2, representing a location in a file system as a sequence of path elements separated by a delimiter. Unlike java.io.File, Path is an interface backed by a pluggable FileSystem provider — the default provider maps to the OS file system, but ZIP archives, in-memory file systems, and remote file systems can provide their own Path implementations. Path is immutable and does not represent an open file or an existing file system entry; it is purely a path string with structured access and manipulation methods. Path provides substantially better semantics than File: path component access, resolution, relativization, normalization, and iteration over path elements are all first-class operations. Combined with the Files utility class, Path enables all file system operations with proper IOException-throwing semantics. This entry covers all Path construction methods, every path manipulation method in depth, path resolution and relativization with edge cases, normalization and toRealPath(), path iteration and watching, conversion to URI and File, comparison semantics, and cross-platform path handling.
Channels
A channel in Java NIO is an open connection to an I/O entity — a file, a socket, a pipe — that can be read from, written to, or both. Channels differ from streams in being bidirectional (a single FileChannel can both read and write), operating exclusively with ByteBuffers rather than individual bytes, supporting seeking and position-based access (FileChannel), and being capable of non-blocking mode (network channels). The channel hierarchy descends from java.nio.channels.Channel at the root; ReadableByteChannel and WritableByteChannel define the reading and writing contracts; ByteChannel combines both; SeekableByteChannel adds position and size access; FileChannel extends SeekableByteChannel with file-specific operations including memory mapping, file locking, and zero-copy transfers. For network I/O, SocketChannel, ServerSocketChannel, and DatagramChannel support both blocking and non-blocking modes and are registered with a Selector for multiplexed event-driven I/O. This entry covers the complete FileChannel API including map(), lock(), transferTo(), and transferFrom(); the blocking and non-blocking network channels; the Selector and SelectionKey model; Pipe channels for inter-thread communication; and the AsynchronousChannel group for callback-based or Future-based I/O.
Buffers
A Buffer in Java NIO is a fixed-capacity, typed container for data that serves as the intermediary between application code and channels. Every I/O operation in NIO reads data into a buffer or writes data from a buffer — there is no byte-at-a-time API. The Buffer class hierarchy provides typed buffers for every Java primitive: ByteBuffer, CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer, and DoubleBuffer. ByteBuffer is the foundation type because channels always operate in bytes; the other types are views over a ByteBuffer or are used for in-memory data processing. Every buffer has three state variables — position, limit, and capacity — that control where reads and writes occur and how much data is available. The flip(), clear(), compact(), rewind(), and mark()/reset() operations manipulate these state variables to implement the write-then-read and read-then-write patterns that buffer-based I/O requires. ByteBuffer can be backed by a Java heap array (allocate()) or by off-heap native memory (allocateDirect()), with significant performance implications for I/O operations. This entry covers all buffer state variables and their transitions, every buffer method in depth, heap vs direct buffer performance model, view buffers and byte order, the slice() and duplicate() operations, and the common patterns for buffer-based channel I/O.