File Handling in Java

1. Java IO Overview

Java's file I/O is built on the concept of streams — sequences of data flowing between a source and a destination. There are two fundamental categories:

Category Base Classes Unit Use For
Byte Streams InputStream / OutputStream byte (8-bit) Binary files: images, audio, video
Character Streams Reader / Writer char (16-bit Unicode) Text files: .txt, .csv, .xml

The java.io package contains the traditional I/O classes. The java.nio package (New I/O, Java 7+) provides the modern Path/Files API which is more powerful and concise.

Best practice: Prefer java.nio.file.Files for new code. Use java.io classes when working with older APIs or when wrapping with buffered streams.

2. File Class

The java.io.File class represents a file or directory path. It does not read or write content — it manages metadata and filesystem operations.

import java.io.File;

public class FileClassDemo {
    public static void main(String[] args) throws Exception {
        File file = new File("example.txt");

        // Create a new file
        if (file.createNewFile()) {
            System.out.println("File created: " + file.getName());
        } else {
            System.out.println("File already exists.");
        }

        // File metadata
        System.out.println("Absolute path: " + file.getAbsolutePath());
        System.out.println("Exists?  " + file.exists());
        System.out.println("Is file? " + file.isFile());
        System.out.println("Is dir?  " + file.isDirectory());
        System.out.println("Size:    " + file.length() + " bytes");
        System.out.println("Readable? " + file.canRead());
        System.out.println("Writable? " + file.canWrite());

        // Directory operations
        File dir = new File("myFolder");
        dir.mkdir();          // create single directory
        // dir.mkdirs();      // create parent directories too

        File dir2 = new File("parent/child/grandchild");
        dir2.mkdirs();

        // List directory contents
        File currentDir = new File(".");
        String[] contents = currentDir.list();
        System.out.println("Files in current directory:");
        if (contents != null) {
            for (String name : contents) {
                System.out.println("  " + name);
            }
        }

        // Delete the file
        if (file.delete()) {
            System.out.println("File deleted.");
        }
    }
}
File created: example.txt
Absolute path: C:\projects\example.txt
Exists? true
Is file? true
Is dir? false
Size: 0 bytes

3. FileInputStream and FileOutputStream

These byte-stream classes read and write raw bytes. Always close them in a finally block or use try-with-resources to prevent resource leaks.

import java.io.*;

public class ByteStreamDemo {
    public static void main(String[] args) {
        // Write bytes to a file
        try (FileOutputStream fos = new FileOutputStream("data.bin")) {
            byte[] data = {72, 101, 108, 108, 111}; // "Hello" in ASCII
            fos.write(data);
            fos.write('!');
            System.out.println("Bytes written successfully.");
        } catch (IOException e) {
            System.err.println("Write error: " + e.getMessage());
        }

        // Read bytes from a file
        try (FileInputStream fis = new FileInputStream("data.bin")) {
            int byteValue;
            System.out.print("Read bytes: ");
            while ((byteValue = fis.read()) != -1) {
                System.out.print((char) byteValue);
            }
            System.out.println();
        } catch (FileNotFoundException e) {
            System.err.println("File not found: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("Read error: " + e.getMessage());
        }

        // Copy a file using byte streams
        try (FileInputStream in  = new FileInputStream("data.bin");
             FileOutputStream out = new FileOutputStream("data_copy.bin")) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
            System.out.println("File copied successfully.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Bytes written successfully.
Read bytes: Hello!
File copied successfully.

4. FileReader and FileWriter

Character-based streams that read and write text files using the platform's default charset (or a specified one). Ideal for plain text content.

import java.io.*;

public class CharStreamDemo {
    public static void main(String[] args) {
        // Write characters to a text file
        try (FileWriter fw = new FileWriter("notes.txt")) {
            fw.write("Java File Handling\n");
            fw.write("Line 2: FileWriter example\n");
            fw.write("Line 3: Written with try-with-resources\n");
            System.out.println("Text written to notes.txt");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Read characters from a text file
        try (FileReader fr = new FileReader("notes.txt")) {
            int ch;
            System.out.println("File contents:");
            while ((ch = fr.read()) != -1) {
                System.out.print((char) ch);
            }
        } catch (FileNotFoundException e) {
            System.err.println("File not found: " + e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Append to an existing file (second argument = true)
        try (FileWriter fw = new FileWriter("notes.txt", true)) {
            fw.write("Line 4: Appended line\n");
            System.out.println("Appended successfully.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Text written to notes.txt
File contents:
Java File Handling
Line 2: FileWriter example
Line 3: Written with try-with-resources
Appended successfully.

5. BufferedReader and BufferedWriter

Wrapping FileReader/FileWriter in BufferedReader/BufferedWriter adds an in-memory buffer, dramatically reducing the number of disk reads and writes. readLine() makes line-by-line reading trivial.

import java.io.*;

public class BufferedStreamDemo {
    public static void main(String[] args) throws IOException {
        // Buffered writing
        try (BufferedWriter bw = new BufferedWriter(
                new FileWriter("buffered.txt"))) {
            bw.write("First line");
            bw.newLine();          // OS-independent line separator
            bw.write("Second line");
            bw.newLine();
            bw.write("Third line");
            System.out.println("Buffered write complete.");
        }

        // Buffered reading — line by line
        try (BufferedReader br = new BufferedReader(
                new FileReader("buffered.txt"))) {
            String line;
            int lineNum = 1;
            while ((line = br.readLine()) != null) {
                System.out.println("Line " + lineNum++ + ": " + line);
            }
        }
    }
}
Buffered write complete.
Line 1: First line
Line 2: Second line
Line 3: Third line

Performance: Always wrap FileReader/FileWriter with Buffered versions for real applications. Unbuffered reads hit the disk once per character — buffered reads load a chunk at a time.

6. Reading a File Line by Line

Three common approaches, each suited to different scenarios:

Method 1: BufferedReader (classic, memory-efficient)

import java.io.*;

try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}

Method 2: Scanner (flexible tokenizing)

import java.util.Scanner;
import java.io.File;

try (Scanner sc = new Scanner(new File("data.txt"))) {
    while (sc.hasNextLine()) {
        String line = sc.nextLine();
        System.out.println(line);
    }
}

Method 3: Files.readAllLines (NIO — simplest, loads all into memory)

import java.nio.file.*;
import java.util.List;

List<String> lines = Files.readAllLines(Paths.get("data.txt"));
for (String line : lines) {
    System.out.println(line);
}

// Java 8+ stream approach (lazy — great for large files)
Files.lines(Paths.get("data.txt"))
     .filter(l -> l.startsWith("ERROR"))
     .forEach(System.out::println);

Memory warning: Files.readAllLines() loads the entire file into a List. For large files (hundreds of MB), use BufferedReader or Files.lines() (which is lazy) instead.

7. Writing to a File

Several options exist depending on your needs:

FileWriter with Append Mode

import java.io.*;

// Overwrite (default)
try (FileWriter fw = new FileWriter("log.txt")) {
    fw.write("Session started\n");
}

// Append mode
try (FileWriter fw = new FileWriter("log.txt", true)) {
    fw.write("New entry added\n");
}

PrintWriter (formatted output)

import java.io.*;

try (PrintWriter pw = new PrintWriter(new FileWriter("report.txt"))) {
    pw.println("Report Title");
    pw.printf("Value: %.2f%n", 3.14159);
    pw.println("Done.");
    System.out.println("PrintWriter write complete.");
}

Files.write (NIO — simplest)

import java.nio.file.*;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;

// Write list of lines (overwrites)
Files.write(Paths.get("output.txt"),
    Arrays.asList("Line A", "Line B", "Line C"),
    StandardCharsets.UTF_8);

// Append to existing file
Files.write(Paths.get("output.txt"),
    Arrays.asList("Appended line"),
    StandardCharsets.UTF_8,
    StandardOpenOption.APPEND,
    StandardOpenOption.CREATE);
PrintWriter write complete.

8. FilePermission and FileDescriptor

FilePermission (from java.io) is used in conjunction with Java's security manager to control which files a program can access. It defines a target path and a set of allowed actions (read, write, execute, delete).

import java.io.FilePermission;

// Grant read permission on a specific file
FilePermission readPerm = new FilePermission("/data/config.txt", "read");

// Grant read and write on all files in /logs
FilePermission logPerm = new FilePermission("/logs/*", "read,write");

// Check if one permission implies another
System.out.println(logPerm.implies(readPerm)); // false (different path)

FileDescriptor is a low-level handle to an underlying OS file, socket, or other resource. The JVM uses it internally; you rarely need it directly. The three standard constants are:

import java.io.FileDescriptor;

FileDescriptor stdin  = FileDescriptor.in;   // standard input
FileDescriptor stdout = FileDescriptor.out;  // standard output
FileDescriptor stderr = FileDescriptor.err;  // standard error

// Force OS to flush data from kernel buffers to disk
try (java.io.FileOutputStream fos = new java.io.FileOutputStream("critical.txt")) {
    fos.write("important data".getBytes());
    fos.getFD().sync(); // flush all OS buffers — ensures data is on disk
}

When they matter: FilePermission is relevant in enterprise applications with a security manager enforcing policy files. FileDescriptor.sync() matters in financial or transactional code where data must survive a power failure.

9. Java NIO — Path and Files

The java.nio.file package (Java 7+) provides a modern, expressive API. Path is the NIO equivalent of File, and Files provides utility methods for common operations.

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

public class NIODemo {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("nio_example.txt");

        // Check existence
        System.out.println("Exists: " + Files.exists(path));

        // Write content
        Files.writeString(path, "Hello from NIO!\nSecond line.\n",
                          StandardCharsets.UTF_8);

        // Read all bytes
        byte[] bytes = Files.readAllBytes(path);
        System.out.println("Bytes read: " + bytes.length);

        // Read as string (Java 11+)
        String content = Files.readString(path);
        System.out.println("Content:\n" + content);

        // File metadata
        System.out.println("Size: " + Files.size(path) + " bytes");
        System.out.println("Last modified: " + Files.getLastModifiedTime(path));

        // Copy a file
        Path dest = Paths.get("nio_copy.txt");
        Files.copy(path, dest, StandardCopyOption.REPLACE_EXISTING);
        System.out.println("Copied to: " + dest);

        // Move (rename) a file
        Path moved = Paths.get("nio_moved.txt");
        Files.move(dest, moved, StandardCopyOption.REPLACE_EXISTING);
        System.out.println("Moved to: " + moved);

        // Delete files
        Files.deleteIfExists(path);
        Files.deleteIfExists(moved);
        System.out.println("Cleanup done.");
    }
}
Exists: false
Bytes read: 28
Content:
Hello from NIO!
Second line.
Size: 28 bytes
Copied to: nio_copy.txt
Moved to: nio_moved.txt
Cleanup done.

10. Walking a Directory

Files.walk() traverses a directory tree recursively. Files.list() returns only the immediate children of a directory (non-recursive).

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

public class DirectoryWalkDemo {
    public static void main(String[] args) throws IOException {
        Path startDir = Paths.get("C:/myproject");

        System.out.println("=== All files recursively (Files.walk) ===");
        try (java.util.stream.Stream<Path> stream = Files.walk(startDir)) {
            stream.filter(Files::isRegularFile)
                  .forEach(p -> System.out.println(p));
        }

        System.out.println("\n=== Only Java files ===");
        try (java.util.stream.Stream<Path> stream = Files.walk(startDir)) {
            stream.filter(p -> p.toString().endsWith(".java"))
                  .forEach(p -> System.out.println(p.getFileName()));
        }

        System.out.println("\n=== Immediate children only (Files.list) ===");
        try (java.util.stream.Stream<Path> stream = Files.list(startDir)) {
            stream.forEach(p -> System.out.println(
                p.getFileName() + (Files.isDirectory(p) ? " [DIR]" : " [FILE]")));
        }

        // Count total files
        long count;
        try (java.util.stream.Stream<Path> stream = Files.walk(startDir)) {
            count = stream.filter(Files::isRegularFile).count();
        }
        System.out.println("\nTotal files: " + count);
    }
}
=== All files recursively (Files.walk) ===
C:\myproject\src\Main.java
C:\myproject\src\Utils.java
C:\myproject\pom.xml

=== Only Java files ===
Main.java
Utils.java

Total files: 3

11. try-with-resources for IO

Introduced in Java 7, try-with-resources automatically closes any AutoCloseable resource at the end of the block — even if an exception is thrown. This is the preferred way to handle all I/O resources and eliminates boilerplate finally blocks.

import java.io.*;

public class TryWithResourcesDemo {

    // Old way — verbose and error-prone
    public static void oldWay(String path) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(path));
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try { br.close(); }
                catch (IOException e) { e.printStackTrace(); }
            }
        }
    }

    // Modern way — clean and safe
    public static void modernWay(String path) {
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // br is automatically closed here
    }

    // Multiple resources — closed in reverse order
    public static void copyFile(String src, String dst) {
        try (BufferedReader br = new BufferedReader(new FileReader(src));
             BufferedWriter bw = new BufferedWriter(new FileWriter(dst))) {
            String line;
            while ((line = br.readLine()) != null) {
                bw.write(line);
                bw.newLine();
            }
            System.out.println("File copied with try-with-resources.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Rule: Any class implementing AutoCloseable (including all java.io streams, JDBC connections, and NIO channels) should be used in try-with-resources. This is non-negotiable in production code.

12. Common IO Exceptions

Knowing which exception signals which problem helps you write better error handling:

Exception When It Occurs Handling Tip
FileNotFoundException File does not exist, or path is a directory, or no read permission. Subclass of IOException. Check file.exists() first or show a user-friendly message.
IOException General I/O failure: disk full, network drive disconnected, corrupted stream. Catch separately after more specific exceptions; log the full stack trace.
SecurityException Security manager denies read/write access. Rare in modern apps; check security policy if running in a sandboxed environment.
EOFException Reached end of file unexpectedly (common with DataInputStream). Use read() == -1 checks instead of letting EOF throw.
import java.io.*;

public class ExceptionHandlingDemo {
    public static void readFile(String path) {
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (FileNotFoundException e) {
            // Specific: file doesn't exist
            System.err.println("ERROR: File not found at path: " + path);
            System.err.println("Please check the file path and try again.");
        } catch (IOException e) {
            // General: disk error, permission denied, etc.
            System.err.println("ERROR: Failed to read file: " + e.getMessage());
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        readFile("exists.txt");          // reads fine
        readFile("nonexistent.txt");     // FileNotFoundException
    }
}

Continue learning