⚡IntermediateFile I/O

Reading Files

Read text and binary files using the modern java.nio.file.Files API and classic BufferedReader.

Overview

Java provides two main APIs for file I/O. The modern java.nio.file package (introduced in Java 7) is the recommended approach. Files.readAllLines() and Files.readString() are simple and concise. For large files, Files.lines() returns a Stream<String> that reads lazily. The classic java.io package uses InputStream, Reader, and BufferedReader. Understanding both helps you read older code and work with libraries that use the classic API. All file operations can throw IOException, so use try-with-resources to ensure streams are always closed.

Key Points

  • •Path.of("filename.txt") or Paths.get("filename.txt") creates a Path
  • •Files.readString(path) reads the entire file as a String (Java 11+)
  • •Files.readAllLines(path) reads all lines into a List<String>
  • •Files.lines(path) returns a Stream<String> for lazy line-by-line reading
  • •BufferedReader wraps a FileReader for efficient character reading
  • •BufferedReader.readLine() returns null at end-of-file
  • •Always use try-with-resources: readers and streams must be closed
  • •Specify charset explicitly: StandardCharsets.UTF_8

Examples

Reading a file with Files (modern NIO)

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;

public class Main {
    public static void main(String[] args) throws IOException {
        // Create a temp file for demonstration
        Path path = Files.createTempFile("demo", ".txt");
        Files.writeString(path, "line one\nline two\nline three");

        // Read all lines as a list
        List<String> lines = Files.readAllLines(path);
        System.out.println("Lines: " + lines.size());
        for (String line : lines) {
            System.out.println("> " + line);
        }

        // Read entire file as one String
        String content = Files.readString(path);
        System.out.println("Content length: " + content.length());

        // Lazy stream for large files
        System.out.println("Words per line:");
        try (var stream = Files.lines(path)) {
            stream.forEach(line -> System.out.println(line + " -> " + line.split("\\s+").length + " word(s)"));
        }

        Files.delete(path); // cleanup
    }
}

Files.lines() returns a Stream that holds a reader. Always close it with try-with-resources. Files.readAllLines() closes the file automatically after reading.

Reading with BufferedReader (classic API)

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

public class Main {
    public static void main(String[] args) throws IOException {
        Path path = Files.createTempFile("demo", ".txt");
        Files.writeString(path, "alpha\nbeta\ngamma");

        // BufferedReader with try-with-resources
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream(path.toFile()), StandardCharsets.UTF_8))) {
            String line;
            int lineNum = 1;
            while ((line = reader.readLine()) != null) {
                System.out.printf("%d: %s%n", lineNum++, line);
            }
        }

        // Simpler: Files.newBufferedReader
        try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            reader.lines().forEach(System.out::println);
        }

        Files.delete(path);
    }
}

readLine() returns null at end of file, not an exception. The while ((line = reader.readLine()) != null) idiom is the standard loop pattern for BufferedReader.

Processing a CSV-like file with streams

import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) throws IOException {
        Path path = Files.createTempFile("data", ".csv");
        Files.writeString(path, "Alice,30,Engineer\nBob,25,Designer\nCarol,35,Manager\nDave,28,Engineer");

        // Parse and filter: find all Engineers
        try (var lines = Files.lines(path)) {
            Map<String, List<String[]>> byRole = lines
                .map(l -> l.split(","))
                .filter(parts -> parts.length == 3)
                .collect(Collectors.groupingBy(parts -> parts[2]));

            byRole.forEach((role, people) -> {
                System.out.println(role + ":");
                people.forEach(p -> System.out.println("  " + p[0] + " (age " + p[1] + ")"));
            });
        }

        Files.delete(path);
    }
}

Files.lines() combined with stream operations is a concise way to process large files line by line without loading the entire file into memory.

Common Mistakes

  • Not closing Files.lines() stream. It holds an open file handle. Always wrap in try-with-resources
  • Using FileReader directly without BufferedReader. FileReader reads character by character which is very slow. Wrap it in BufferedReader
  • Hardcoding the platform default charset. Always specify StandardCharsets.UTF_8 or Charset.forName("UTF-8") explicitly to avoid encoding bugs on different platforms

Note

For small to medium files, Files.readAllLines() or Files.readString() is the simplest approach. For large files (hundreds of MB), use Files.lines() with a stream pipeline to avoid loading everything into memory.

Exercises

Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.

Loading editor…

Completed: 0 / 4 exercises

Knowledge Check

Question 1 of 4

Which method reads an entire small file into a single String?