14.2 Files API Operations

Master the Files utility class for comprehensive file and directory operations.

Reading Files

Reading Text Files:

import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;

// Read entire file as string (small files)
Path configFile = Path.of("config.txt");
String content = Files.readString(configFile);
System.out.println(content);

// With specific charset
String utf16Content = Files.readString(configFile, StandardCharsets.UTF_16);

// Read all lines into List
List<String> lines = Files.readAllLines(configFile);
for (String line : lines) {
    System.out.println(line);
}

// Read lines as stream (large files)
try (Stream<String> lineStream = Files.lines(configFile)) {
    lineStream
        .filter(line -> !line.startsWith("#"))
        .map(String::trim)
        .forEach(System.out::println);
}

// With charset
try (Stream<String> stream = Files.lines(configFile, StandardCharsets.UTF_8)) {
    long count = stream.count();
    System.out.println("Line count: " + count);
}

Reading Binary Files:

// Read entire file as byte array (small files)
Path imageFile = Path.of("photo.jpg");
byte[] imageData = Files.readAllBytes(imageFile);
System.out.println("Image size: " + imageData.length + " bytes");

// Read with InputStream (large files)
try (InputStream in = Files.newInputStream(imageFile)) {
    byte[] buffer = new byte[8192];
    int bytesRead;

    while ((bytesRead = in.read(buffer)) != -1) {
        // Process buffer
        processBytes(buffer, bytesRead);
    }
}

// With StandardOpenOption
try (InputStream in = Files.newInputStream(
        imageFile, 
        StandardOpenOption.READ)) {
    // Read file
}

BufferedReader for Large Files:

Path largeFile = Path.of("large-dataset.csv");

try (BufferedReader reader = Files.newBufferedReader(largeFile)) {
    String line;
    int lineNum = 0;

    while ((line = reader.readLine()) != null) {
        lineNum++;
        // Process line
        if (lineNum % 10000 == 0) {
            System.out.println("Processed " + lineNum + " lines");
        }
    }
}

// With charset
try (BufferedReader reader = Files.newBufferedReader(
        largeFile, 
        StandardCharsets.UTF_8)) {
    // Read content
}

Writing Files

Writing Text Files:

Path outputFile = Path.of("output.txt");

// Write string to file (overwrites by default)
String content = "Hello, World!\nThis is a test.";
Files.writeString(outputFile, content);

// With charset
Files.writeString(outputFile, content, StandardCharsets.UTF_8);

// With options
Files.writeString(
    outputFile, 
    content,
    StandardCharsets.UTF_8,
    StandardOpenOption.CREATE,
    StandardOpenOption.APPEND
);

// Write lines
List<String> lines = List.of(
    "Line 1",
    "Line 2",
    "Line 3"
);
Files.write(outputFile, lines);

// With charset and options
Files.write(
    outputFile,
    lines,
    StandardCharsets.UTF_8,
    StandardOpenOption.CREATE,
    StandardOpenOption.TRUNCATE_EXISTING
);

Writing Binary Files:

Path binaryFile = Path.of("data.bin");

// Write byte array
byte[] data = {0x48, 0x65, 0x6C, 0x6C, 0x6F}; // "Hello"
Files.write(binaryFile, data);

// With options
Files.write(
    binaryFile,
    data,
    StandardOpenOption.CREATE,
    StandardOpenOption.WRITE
);

// Write with OutputStream
try (OutputStream out = Files.newOutputStream(binaryFile)) {
    out.write(data);
    out.flush();
}

// With StandardOpenOption
try (OutputStream out = Files.newOutputStream(
        binaryFile,
        StandardOpenOption.CREATE,
        StandardOpenOption.TRUNCATE_EXISTING,
        StandardOpenOption.WRITE)) {
    out.write(data);
}

BufferedWriter for Large Files:

Path logFile = Path.of("application.log");

try (BufferedWriter writer = Files.newBufferedWriter(
        logFile,
        StandardCharsets.UTF_8,
        StandardOpenOption.CREATE,
        StandardOpenOption.APPEND)) {

    for (int i = 0; i < 100000; i++) {
        writer.write("Log entry " + i);
        writer.newLine();

        if (i % 1000 == 0) {
            writer.flush(); // Periodic flush
        }
    }
}

StandardOpenOption Combinations:

// Common combinations
Path file = Path.of("data.txt");

// Create new file (fails if exists)
Files.writeString(file, "content", 
    StandardOpenOption.CREATE_NEW,
    StandardOpenOption.WRITE);

// Create or overwrite
Files.writeString(file, "content",
    StandardOpenOption.CREATE,
    StandardOpenOption.TRUNCATE_EXISTING,
    StandardOpenOption.WRITE);

// Append to existing (create if doesn't exist)
Files.writeString(file, "more content",
    StandardOpenOption.CREATE,
    StandardOpenOption.APPEND);

// Write with sync (immediate flush to disk)
Files.writeString(file, "important data",
    StandardOpenOption.WRITE,
    StandardOpenOption.SYNC);

// Write atomically (on supported file systems)
Files.writeString(file, "atomic content",
    StandardOpenOption.WRITE,
    StandardOpenOption.DSYNC);

Copying Files

Basic Copy Operations:

Path source = Path.of("source.txt");
Path target = Path.of("target.txt");

// Simple copy (fails if target exists)
Files.copy(source, target);

// Copy with replace existing
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);

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

// Copy to OutputStream
try (OutputStream out = new FileOutputStream("output.bin")) {
    Files.copy(source, out);
}

// Copy from InputStream
try (InputStream in = new FileInputStream("input.bin")) {
    long bytesCopied = Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
    System.out.println("Copied " + bytesCopied + " bytes");
}

Copying Directories:

/**
 * Copy directory recursively
 */
public static void copyDirectory(Path source, Path target) throws IOException {
    Files.walk(source).forEach(sourcePath -> {
        try {
            Path targetPath = target.resolve(source.relativize(sourcePath));

            if (Files.isDirectory(sourcePath)) {
                Files.createDirectories(targetPath);
            } else {
                Files.copy(sourcePath, targetPath, 
                    StandardCopyOption.REPLACE_EXISTING);
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    });
}

// Usage
Path sourceDir = Path.of("/source/directory");
Path targetDir = Path.of("/target/directory");
copyDirectory(sourceDir, targetDir);

Moving Files

Move Operations:

Path source = Path.of("/tmp/oldfile.txt");
Path target = Path.of("/var/data/newfile.txt");

// Simple move (fails if target exists)
Files.move(source, target);

// Move with replace
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);

// Atomic move (on same filesystem)
Files.move(source, target,
    StandardCopyOption.ATOMIC_MOVE,
    StandardCopyOption.REPLACE_EXISTING);

// Rename file in same directory
Path file = Path.of("/data/oldname.txt");
Path renamed = file.resolveSibling("newname.txt");
Files.move(file, renamed);

Atomic Move for Safety:

/**
 * Safe atomic file replacement
 */
public static void safeReplace(Path tempFile, Path targetFile) throws IOException {
    try {
        // Atomic move replaces target
        Files.move(tempFile, targetFile,
            StandardCopyOption.ATOMIC_MOVE,
            StandardCopyOption.REPLACE_EXISTING);
    } catch (AtomicMoveNotSupportedException e) {
        // Fallback: copy then delete
        Files.copy(tempFile, targetFile, StandardCopyOption.REPLACE_EXISTING);
        Files.delete(tempFile);
    }
}

// Usage pattern for safe writes
Path tempFile = Files.createTempFile("upload-", ".tmp");
try {
    // Write to temp file
    Files.writeString(tempFile, "important data");

    // Atomically replace target
    safeReplace(tempFile, Path.of("/var/data/important.txt"));
} catch (IOException e) {
    // Clean up temp file on error
    Files.deleteIfExists(tempFile);
    throw e;
}

Deleting Files

Delete Operations:

Path file = Path.of("unwanted.txt");

// Delete (throws exception if doesn't exist)
Files.delete(file);

// Delete if exists (returns boolean)
boolean deleted = Files.deleteIfExists(file);
System.out.println("File deleted: " + deleted);

// Delete empty directory
Path emptyDir = Path.of("/tmp/emptydir");
Files.delete(emptyDir); // Fails if not empty

Deleting Directory Trees:

/**
 * Delete directory recursively
 */
public static void deleteDirectory(Path directory) throws IOException {
    if (!Files.exists(directory)) {
        return;
    }

    Files.walk(directory)
        .sorted(Comparator.reverseOrder()) // Delete files before directories
        .forEach(path -> {
            try {
                Files.delete(path);
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        });
}

// Usage
Path dir = Path.of("/tmp/old-data");
deleteDirectory(dir);

Safe Deletion with Verification:

public static boolean safeDelete(Path path) {
    try {
        if (!Files.exists(path)) {
            return false;
        }

        // Verify we can delete (check permissions)
        if (!Files.isWritable(path.getParent())) {
            System.err.println("No write permission on parent directory");
            return false;
        }

        // Delete
        Files.delete(path);

        // Verify deletion
        if (Files.exists(path)) {
            System.err.println("File still exists after deletion");
            return false;
        }

        return true;
    } catch (IOException e) {
        System.err.println("Deletion failed: " + e.getMessage());
        return false;
    }
}

Creating Directories

Directory Creation:

// Create single directory
Path dir = Path.of("/tmp/newdir");
Files.createDirectory(dir);

// Create directory tree
Path deepDir = Path.of("/tmp/a/b/c/d");
Files.createDirectories(deepDir); // Creates all parent directories

// Check before creating
if (!Files.exists(dir)) {
    Files.createDirectories(dir);
}

// With attributes (POSIX)
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---");
FileAttribute<Set<PosixFilePermission>> attrs = 
    PosixFilePermissions.asFileAttribute(perms);

Path secureDir = Path.of("/tmp/secure");
Files.createDirectory(secureDir, attrs);

File Attributes

Basic Attributes:

Path file = Path.of("document.txt");

// Read basic attributes
BasicFileAttributes attrs = Files.readAttributes(file, BasicFileAttributes.class);

System.out.println("Size: " + attrs.size() + " bytes");
System.out.println("Created: " + attrs.creationTime());
System.out.println("Modified: " + attrs.lastModifiedTime());
System.out.println("Accessed: " + attrs.lastAccessTime());
System.out.println("Is directory: " + attrs.isDirectory());
System.out.println("Is regular file: " + attrs.isRegularFile());
System.out.println("Is symbolic link: " + attrs.isSymbolicLink());

// File key (unique identifier)
Object fileKey = attrs.fileKey();
System.out.println("File key: " + fileKey);

Quick Attribute Methods:

Path path = Path.of("file.txt");

// Common checks
boolean exists = Files.exists(path);
boolean notExists = Files.notExists(path);
boolean isDirectory = Files.isDirectory(path);
boolean isRegularFile = Files.isRegularFile(path);
boolean isSymbolicLink = Files.isSymbolicLink(path);
boolean isHidden = Files.isHidden(path);
boolean isReadable = Files.isReadable(path);
boolean isWritable = Files.isWritable(path);
boolean isExecutable = Files.isExecutable(path);

// File size
long size = Files.size(path);

// Last modified time
FileTime lastModified = Files.getLastModifiedTime(path);

// Set last modified time
FileTime newTime = FileTime.fromMillis(System.currentTimeMillis());
Files.setLastModifiedTime(path, newTime);

POSIX Attributes (Unix/Linux):

if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
    Path file = Path.of("script.sh");

    // Read POSIX attributes
    PosixFileAttributes attrs = Files.readAttributes(file, PosixFileAttributes.class);

    System.out.println("Owner: " + attrs.owner());
    System.out.println("Group: " + attrs.group());
    System.out.println("Permissions: " + PosixFilePermissions.toString(attrs.permissions()));

    // Set permissions
    Set<PosixFilePermission> perms = new HashSet<>();
    perms.add(PosixFilePermission.OWNER_READ);
    perms.add(PosixFilePermission.OWNER_WRITE);
    perms.add(PosixFilePermission.OWNER_EXECUTE);
    perms.add(PosixFilePermission.GROUP_READ);
    perms.add(PosixFilePermission.GROUP_EXECUTE);

    Files.setPosixFilePermissions(file, perms);

    // Or use string notation
    Set<PosixFilePermission> perms2 = PosixFilePermissions.fromString("rwxr-xr-x");
    Files.setPosixFilePermissions(file, perms2);

    // Change owner
    UserPrincipal owner = FileSystems.getDefault()
        .getUserPrincipalLookupService()
        .lookupPrincipalByName("username");
    Files.setOwner(file, owner);

    // Change group
    GroupPrincipal group = FileSystems.getDefault()
        .getUserPrincipalLookupService()
        .lookupPrincipalByGroupName("groupname");
    Files.getFileAttributeView(file, PosixFileAttributeView.class)
        .setGroup(group);
}

DOS Attributes (Windows):

if (FileSystems.getDefault().supportedFileAttributeViews().contains("dos")) {
    Path file = Path.of("document.txt");

    // Read DOS attributes
    DosFileAttributes attrs = Files.readAttributes(file, DosFileAttributes.class);

    System.out.println("Hidden: " + attrs.isHidden());
    System.out.println("Archive: " + attrs.isArchive());
    System.out.println("System: " + attrs.isSystem());
    System.out.println("Read-only: " + attrs.isReadOnly());

    // Set DOS attributes
    DosFileAttributeView view = Files.getFileAttributeView(
        file, 
        DosFileAttributeView.class
    );

    view.setHidden(true);
    view.setArchive(true);
    view.setReadOnly(false);
    view.setSystem(false);
}

Generic Attributes:

Path file = Path.of("data.txt");

// Get single attribute
Object size = Files.getAttribute(file, "basic:size");
Object modified = Files.getAttribute(file, "basic:lastModifiedTime");

// Get multiple attributes
Map<String, Object> attrs = Files.readAttributes(file, "basic:*");
attrs.forEach((key, value) -> {
    System.out.println(key + " = " + value);
});

// Set attribute
Files.setAttribute(file, "basic:lastModifiedTime", 
    FileTime.fromMillis(System.currentTimeMillis()));

// POSIX attributes
if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
    Object permissions = Files.getAttribute(file, "posix:permissions");
    System.out.println("Permissions: " + permissions);

    Files.setAttribute(file, "posix:permissions", 
        PosixFilePermissions.fromString("rw-r--r--"));
}

Real-World Example: FileManager

import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Comprehensive file management utility
 */
public class FileManager {

    /**
     * Copy with progress callback
     */
    public static void copyWithProgress(
            Path source, 
            Path target,
            ProgressCallback callback) throws IOException {

        long totalSize = Files.size(source);
        long copiedBytes = 0;

        try (InputStream in = Files.newInputStream(source);
             OutputStream out = Files.newOutputStream(target,
                 StandardOpenOption.CREATE,
                 StandardOpenOption.TRUNCATE_EXISTING)) {

            byte[] buffer = new byte[8192];
            int bytesRead;

            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
                copiedBytes += bytesRead;

                if (callback != null) {
                    callback.onProgress(copiedBytes, totalSize);
                }
            }
        }
    }

    /**
     * Move with fallback
     */
    public static void moveWithFallback(Path source, Path target) throws IOException {
        try {
            // Try atomic move first
            Files.move(source, target,
                StandardCopyOption.ATOMIC_MOVE,
                StandardCopyOption.REPLACE_EXISTING);
        } catch (AtomicMoveNotSupportedException e) {
            // Fallback to copy and delete
            Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
            Files.delete(source);
        }
    }

    /**
     * Safe write with backup
     */
    public static void safeWrite(Path file, String content) throws IOException {
        Path backup = null;

        // Create backup if file exists
        if (Files.exists(file)) {
            backup = file.resolveSibling(file.getFileName() + ".bak");
            Files.copy(file, backup, StandardCopyOption.REPLACE_EXISTING);
        }

        try {
            // Write to temp file first
            Path tempFile = Files.createTempFile(
                file.getParent(),
                "temp-",
                ".tmp"
            );

            try {
                Files.writeString(tempFile, content);

                // Atomic replace
                Files.move(tempFile, file,
                    StandardCopyOption.ATOMIC_MOVE,
                    StandardCopyOption.REPLACE_EXISTING);

                // Success - delete backup
                if (backup != null) {
                    Files.deleteIfExists(backup);
                }
            } finally {
                // Clean up temp file if still exists
                Files.deleteIfExists(tempFile);
            }
        } catch (IOException e) {
            // Restore from backup on failure
            if (backup != null && Files.exists(backup)) {
                Files.copy(backup, file, StandardCopyOption.REPLACE_EXISTING);
            }
            throw e;
        }
    }

    /**
     * Calculate directory size
     */
    public static long calculateDirectorySize(Path directory) throws IOException {
        AtomicLong size = new AtomicLong(0);

        Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                size.addAndGet(attrs.size());
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) {
                // Skip inaccessible files
                System.err.println("Skipped: " + file + " (" + exc.getMessage() + ")");
                return FileVisitResult.CONTINUE;
            }
        });

        return size.get();
    }

    /**
     * Find files by extension
     */
    public static List<Path> findFilesByExtension(
            Path directory,
            String extension) throws IOException {

        List<Path> results = new ArrayList<>();

        Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                if (file.toString().endsWith("." + extension)) {
                    results.add(file);
                }
                return FileVisitResult.CONTINUE;
            }
        });

        return results;
    }

    /**
     * Find old files
     */
    public static List<Path> findOldFiles(
            Path directory,
            int daysOld) throws IOException {

        Instant cutoff = Instant.now().minusSeconds(daysOld * 86400L);
        List<Path> oldFiles = new ArrayList<>();

        Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                if (attrs.lastModifiedTime().toInstant().isBefore(cutoff)) {
                    oldFiles.add(file);
                }
                return FileVisitResult.CONTINUE;
            }
        });

        return oldFiles;
    }

    /**
     * Get file info summary
     */
    public static String getFileInfo(Path path) throws IOException {
        BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);

        StringBuilder info = new StringBuilder();
        info.append("Path: ").append(path.toAbsolutePath()).append("\n");
        info.append("Size: ").append(formatSize(attrs.size())).append("\n");
        info.append("Type: ");

        if (attrs.isDirectory()) {
            info.append("Directory\n");
        } else if (attrs.isRegularFile()) {
            info.append("Regular File\n");
        } else if (attrs.isSymbolicLink()) {
            info.append("Symbolic Link\n");
        } else {
            info.append("Other\n");
        }

        info.append("Created: ").append(attrs.creationTime()).append("\n");
        info.append("Modified: ").append(attrs.lastModifiedTime()).append("\n");
        info.append("Accessed: ").append(attrs.lastAccessTime()).append("\n");

        // POSIX attributes if available
        if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
            PosixFileAttributes posixAttrs = Files.readAttributes(path, PosixFileAttributes.class);
            info.append("Owner: ").append(posixAttrs.owner()).append("\n");
            info.append("Group: ").append(posixAttrs.group()).append("\n");
            info.append("Permissions: ")
                .append(PosixFilePermissions.toString(posixAttrs.permissions()))
                .append("\n");
        }

        return info.toString();
    }

    private static String formatSize(long bytes) {
        if (bytes < 1024) return bytes + " B";
        int exp = (int) (Math.log(bytes) / Math.log(1024));
        String pre = "KMGTPE".charAt(exp - 1) + "";
        return String.format("%.2f %sB", bytes / Math.pow(1024, exp), pre);
    }

    @FunctionalInterface
    public interface ProgressCallback {
        void onProgress(long current, long total);
    }

    // Example usage
    public static void main(String[] args) throws IOException {
        // Copy with progress
        Path source = Path.of("large-file.zip");
        Path target = Path.of("copy-of-large-file.zip");

        copyWithProgress(source, target, (current, total) -> {
            int percent = (int) ((current * 100) / total);
            System.out.printf("\rCopying: %d%%", percent);
        });
        System.out.println("\nCopy complete!");

        // Safe write
        Path config = Path.of("config.properties");
        safeWrite(config, "key=value\nfoo=bar\n");

        // Directory size
        Path dir = Path.of("/var/log");
        long size = calculateDirectorySize(dir);
        System.out.println("Directory size: " + formatSize(size));

        // Find files
        List<Path> javaFiles = findFilesByExtension(
            Path.of("src"),
            "java"
        );
        System.out.println("Found " + javaFiles.size() + " Java files");

        // Find old files
        List<Path> oldFiles = findOldFiles(Path.of("/tmp"), 7);
        System.out.println("Found " + oldFiles.size() + " files older than 7 days");

        // File info
        System.out.println(getFileInfo(Path.of("README.md")));
    }
}

Best Practices

1. Use Try-With-Resources for Streams:

// Always close streams
try (Stream<String> lines = Files.lines(path)) {
    lines.forEach(System.out::println);
}

2. Choose Appropriate Methods:

// Small files
String content = Files.readString(path);

// Large files
try (Stream<String> lines = Files.lines(path)) {
    // Process line by line
}

3. Use Atomic Operations:

// Atomic file replacement
Files.move(temp, target, StandardCopyOption.ATOMIC_MOVE);

4. Handle Platform Differences:

// Check file system capabilities
if (FileSystems.getDefault()
        .supportedFileAttributeViews()
        .contains("posix")) {
    // Use POSIX attributes
}

5. Verify Operations:

// Verify after critical operations
Files.copy(source, target);
if (!Files.exists(target)) {
    throw new IOException("Copy failed");
}

These Files API operations provide robust tools for all file management needs in modern Java applications.