Skip to main content

09 - Input/Output and NIO.2

I/O in Java: byte and character streams, BufferedReader/Writer, NIO.2 with Path and Files, object serialization and best practices.

What do you think about this article?

At a Glance

I/O in Java: byte and character streams, BufferedReader/Writer, NIO.2 with Path and Files, object serialization and best practices.

Direct answer

The recommended approach for modern I/O operations in Java is the NIO.2 (New Input/Output) API, introduced in Java 7, which provides a more powerful and flexible way to work with files and directories through classes such as Path, Files, and Paths, offering improved performance and functionality over the classic java.io package.

Input/Output in Java

Java offers two main I/O APIs: java.io package (stream-based) and java.nio (New I/O, buffer-based). In this article we'll explore both approaches.

What You Will Learn

  • Byte and character streams
  • File reading and writing
  • BufferedReader and BufferedWriter
  • NIO.2: Path and Files
  • Object serialization
  • I/O best practices

Classic I/O: java.io

Stream Hierarchy

Type Byte Stream Character Stream
Input InputStream Reader
Output OutputStream Writer
File FileInputStream/FileOutputStream FileReader/FileWriter
Buffered BufferedInputStream/BufferedOutputStream BufferedReader/BufferedWriter

Reading Text Files

FileReader and BufferedReader
import java.io.*;

public class StudentFileReader {

    public static void readStudentList(String path) {
        // try-with-resources for automatic closing
        try (BufferedReader reader = new BufferedReader(new FileReader(path))) {

            String line;
            int lineNumber = 1;

            while ((line = reader.readLine()) != null) {
                System.out.println(lineNumber + ": " + line);
                lineNumber++;
            }

        } catch (FileNotFoundException e) {
            System.err.println("File not found: " + path);
        } catch (IOException e) {
            System.err.println("Read error: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        readStudentList("students.txt");
    }
}

Writing Text Files

FileWriter and BufferedWriter
import java.io.*;
import java.util.*;

public class GradeFileWriter {

    public static void saveGrades(String path, Map<String, Integer> grades) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(path))) {

            writer.write("=== GRADE REGISTRY ===");
            writer.newLine();
            writer.newLine();

            for (Map.Entry<String, Integer> entry : grades.entrySet()) {
                String line = entry.getKey() + ": " + entry.getValue();
                writer.write(line);
                writer.newLine();
            }

            writer.newLine();
            writer.write("Generated automatically");
            System.out.println("File saved: " + path);

        } catch (IOException e) {
            System.err.println("Write error: " + e.getMessage());
        }
    }

    // Append mode: adds to existing file
    public static void appendGrade(String path, String student, int grade) {
        try (BufferedWriter writer = new BufferedWriter(
                new FileWriter(path, true))) {  // true = append

            writer.write(student + ": " + grade);
            writer.newLine();

        } catch (IOException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        Map<String, Integer> grades = new LinkedHashMap<>();
        grades.put("John Smith", 85);
        grades.put("Laura White", 92);
        grades.put("Joseph Green", 78);

        saveGrades("grades.txt", grades);
    }
}

Byte Streams

InputStream and OutputStream
import java.io.*;

public class FileCopier {

    public static void copyFile(String source, String destination) {
        try (
            FileInputStream fis = new FileInputStream(source);
            FileOutputStream fos = new FileOutputStream(destination);
            BufferedInputStream bis = new BufferedInputStream(fis);
            BufferedOutputStream bos = new BufferedOutputStream(fos)
        ) {
            byte[] buffer = new byte[8192];  // 8KB buffer
            int bytesRead;

            while ((bytesRead = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, bytesRead);
            }

            System.out.println("File copied successfully");

        } catch (IOException e) {
            System.err.println("Copy error: " + e.getMessage());
        }
    }

    // Read complete byte array
    public static byte[] readAllBytes(String path) throws IOException {
        try (FileInputStream fis = new FileInputStream(path)) {
            return fis.readAllBytes();  // Java 9+
        }
    }

    public static void main(String[] args) {
        copyFile("original.pdf", "copy.pdf");
    }
}

NIO.2: The Modern API

Introduced in Java 7, NIO.2 provides a more modern and powerful API for file and directory operations.

Path and Paths

Working with Paths
import java.nio.file.*;

public class PathManager {

    public static void main(String[] args) {
        // Creating Paths
        Path path1 = Path.of("documents", "students", "grades.txt");
        Path path2 = Paths.get("/home/user/documents/students/grades.txt");
        Path path3 = Path.of("./data/../documents/grades.txt");

        // Path information
        System.out.println("File name: " + path1.getFileName());
        System.out.println("Directory: " + path1.getParent());
        System.out.println("Root: " + path2.getRoot());
        System.out.println("Absolute: " + path1.toAbsolutePath());

        // Normalization (removes . and ..)
        Path normalized = path3.normalize();
        System.out.println("Normalized: " + normalized);

        // Resolution (combining paths)
        Path base = Path.of("/school");
        Path relative = Path.of("students/john/grades.txt");
        Path complete = base.resolve(relative);
        System.out.println("Complete path: " + complete);

        // Relativize
        Path dir1 = Path.of("/school/students");
        Path dir2 = Path.of("/school/teachers/history");
        Path relPath = dir1.relativize(dir2);
        System.out.println("Relative: " + relPath);  // ../teachers/history

        // Iterate components
        for (Path component : path1) {
            System.out.println("Component: " + component);
        }
    }
}

Files: File Operations

Reading and Writing with Files
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class FileOperations {

    public static void main(String[] args) throws Exception {
        Path file = Path.of("students.txt");

        // === WRITING ===

        // Write complete string
        String content = "John Smith\nLaura White\nJoseph Green";
        Files.writeString(file, content);

        // Write list of lines
        List<String> lines = Arrays.asList(
            "John Smith: 85",
            "Laura White: 92",
            "Joseph Green: 78"
        );
        Files.write(Path.of("grades.txt"), lines);

        // Append
        Files.writeString(file, "\nNew Student",
            StandardOpenOption.APPEND);

        // === READING ===

        // Read complete string
        String allContent = Files.readString(file);
        System.out.println(allContent);

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

        // Stream of lines (lazy, for large files)
        Files.lines(file)
             .filter(l -> l.contains("Smith"))
             .forEach(System.out::println);

        // Read bytes
        byte[] bytes = Files.readAllBytes(Path.of("document.pdf"));
    }
}
File and Directory Management
import java.nio.file.*;
import java.nio.file.attribute.*;

public class FileDirectoryManagement {

    public static void main(String[] args) throws Exception {
        Path dir = Path.of("school/students");
        Path file = dir.resolve("roster.txt");

        // === CHECK EXISTENCE AND TYPE ===
        System.out.println("Exists: " + Files.exists(file));
        System.out.println("Is file: " + Files.isRegularFile(file));
        System.out.println("Is directory: " + Files.isDirectory(dir));
        System.out.println("Readable: " + Files.isReadable(file));
        System.out.println("Writable: " + Files.isWritable(file));

        // === CREATE DIRECTORIES ===
        Files.createDirectory(Path.of("new_dir"));  // Single
        Files.createDirectories(dir);  // With parents

        // === CREATE FILE ===
        if (!Files.exists(file)) {
            Files.createFile(file);
        }

        // === COPY ===
        Path copy = Path.of("school/backup/roster_backup.txt");
        Files.createDirectories(copy.getParent());
        Files.copy(file, copy, StandardCopyOption.REPLACE_EXISTING);

        // === MOVE/RENAME ===
        Path newLocation = Path.of("archive/roster.txt");
        Files.createDirectories(newLocation.getParent());
        Files.move(copy, newLocation,
            StandardCopyOption.REPLACE_EXISTING);

        // === DELETE ===
        Files.delete(newLocation);  // Exception if not exists
        Files.deleteIfExists(newLocation);  // Silent

        // === ATTRIBUTES ===
        BasicFileAttributes attrs = Files.readAttributes(
            file, BasicFileAttributes.class
        );
        System.out.println("Size: " + attrs.size());
        System.out.println("Created: " + attrs.creationTime());
        System.out.println("Modified: " + attrs.lastModifiedTime());
    }
}

Exploring Directories

Listing and Searching Files
import java.nio.file.*;
import java.io.IOException;

public class DirectoryExplorer {

    public static void main(String[] args) throws IOException {
        Path dir = Path.of("school");

        // === LIST CONTENTS ===

        // Direct listing (non-recursive)
        System.out.println("=== Directory contents ===");
        try (var stream = Files.list(dir)) {
            stream.forEach(System.out::println);
        }

        // Recursive listing (walk)
        System.out.println("\n=== Recursive walk ===");
        try (var stream = Files.walk(dir)) {
            stream.forEach(System.out::println);
        }

        // Walk with max depth
        try (var stream = Files.walk(dir, 2)) {
            stream.filter(Files::isRegularFile)
                  .forEach(System.out::println);
        }

        // === SEARCH FILES ===

        // With glob pattern
        System.out.println("\n=== .txt files ===");
        try (var stream = Files.newDirectoryStream(dir, "*.txt")) {
            for (Path file : stream) {
                System.out.println(file);
            }
        }

        // Find with predicate
        System.out.println("\n=== Files > 1KB ===");
        try (var stream = Files.find(dir, 10,
                (path, attrs) -> attrs.isRegularFile() &&
                                 attrs.size() > 1024)) {
            stream.forEach(System.out::println);
        }
    }
}

Serialization

Serialization allows you to save Java objects to files and read them back later.

Serializable Class
import java.io.*;

public class Student implements Serializable {
    // UID for versioning
    private static final long serialVersionUID = 1L;

    private String name;
    private String studentId;
    private int enrollmentYear;

    // transient = not serialized
    private transient String temporaryPassword;

    public Student(String name, String studentId, int enrollmentYear) {
        this.name = name;
        this.studentId = studentId;
        this.enrollmentYear = enrollmentYear;
    }

    // Getters
    public String getName() { return name; }
    public String getStudentId() { return studentId; }
    public int getEnrollmentYear() { return enrollmentYear; }

    @Override
    public String toString() {
        return String.format("Student[%s, %s, %d]",
            name, studentId, enrollmentYear);
    }
}
Serialization and Deserialization
import java.io.*;
import java.util.*;

public class SerializationManager {

    // Save an object
    public static void saveStudent(Student student, String file) {
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(file))) {

            oos.writeObject(student);
            System.out.println("Student saved: " + file);

        } catch (IOException e) {
            System.err.println("Save error: " + e.getMessage());
        }
    }

    // Load an object
    public static Student loadStudent(String file) {
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream(file))) {

            return (Student) ois.readObject();

        } catch (IOException | ClassNotFoundException e) {
            System.err.println("Load error: " + e.getMessage());
            return null;
        }
    }

    // Save list of objects
    public static void saveRoster(List<Student> students, String file) {
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(file))) {

            oos.writeObject(students);
            System.out.println("Roster saved: " + students.size() + " students");

        } catch (IOException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    public static List<Student> loadRoster(String file) {
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream(file))) {

            return (List<Student>) ois.readObject();

        } catch (IOException | ClassNotFoundException e) {
            System.err.println("Error: " + e.getMessage());
            return Collections.emptyList();
        }
    }

    public static void main(String[] args) {
        // Save single student
        Student john = new Student("John Smith", "STU001", 2024);
        saveStudent(john, "john.dat");

        // Load
        Student loaded = loadStudent("john.dat");
        System.out.println("Loaded: " + loaded);

        // Save list
        List<Student> students = Arrays.asList(
            new Student("John Smith", "STU001", 2024),
            new Student("Laura White", "STU002", 2024),
            new Student("Joseph Green", "STU003", 2023)
        );
        saveRoster(students, "students.dat");

        // Load list
        List<Student> loadedList = loadRoster("students.dat");
        loadedList.forEach(System.out::println);
    }
}

Complete Example: School Registry System

SchoolRegistry.java
import java.io.*;
import java.nio.file.*;
import java.util.*;

public class SchoolRegistry {

    private static final Path DATA_DIR = Path.of("school_data");
    private static final Path STUDENTS_FILE = DATA_DIR.resolve("students.csv");
    private static final Path GRADES_FILE = DATA_DIR.resolve("grades.csv");

    public static void initialize() throws IOException {
        Files.createDirectories(DATA_DIR);

        if (!Files.exists(STUDENTS_FILE)) {
            Files.writeString(STUDENTS_FILE, "studentId,name,class\n");
        }
        if (!Files.exists(GRADES_FILE)) {
            Files.writeString(GRADES_FILE, "studentId,subject,grade,date\n");
        }
    }

    public static void addStudent(String studentId, String name, String className)
            throws IOException {
        String line = String.format("%s,%s,%s%n", studentId, name, className);
        Files.writeString(STUDENTS_FILE, line, StandardOpenOption.APPEND);
        System.out.println("Student added: " + name);
    }

    public static void recordGrade(String studentId, String subject, int grade)
            throws IOException {
        String date = java.time.LocalDate.now().toString();
        String line = String.format("%s,%s,%d,%s%n", studentId, subject, grade, date);
        Files.writeString(GRADES_FILE, line, StandardOpenOption.APPEND);
        System.out.println("Grade recorded: " + subject + " = " + grade);
    }

    public static void printStudents() throws IOException {
        System.out.println("\n=== STUDENT ROSTER ===");
        Files.lines(STUDENTS_FILE)
             .skip(1)  // Skip header
             .forEach(line -> {
                 String[] parts = line.split(",");
                 if (parts.length >= 3) {
                     System.out.printf("%-10s %-20s %s%n",
                         parts[0], parts[1], parts[2]);
                 }
             });
    }

    public static void printStudentGrades(String studentId) throws IOException {
        System.out.println("\n=== GRADES for " + studentId + " ===");
        Files.lines(GRADES_FILE)
             .skip(1)
             .filter(line -> line.startsWith(studentId + ","))
             .forEach(line -> {
                 String[] parts = line.split(",");
                 if (parts.length >= 4) {
                     System.out.printf("%-15s %s (%s)%n",
                         parts[1], parts[2], parts[3]);
                 }
             });
    }

    public static double calculateAverage(String studentId) throws IOException {
        return Files.lines(GRADES_FILE)
             .skip(1)
             .filter(line -> line.startsWith(studentId + ","))
             .mapToInt(line -> {
                 String[] parts = line.split(",");
                 return Integer.parseInt(parts[2]);
             })
             .average()
             .orElse(0);
    }

    public static void backupData() throws IOException {
        Path backupDir = DATA_DIR.resolve("backup_" +
            java.time.LocalDate.now().toString());
        Files.createDirectories(backupDir);

        Files.copy(STUDENTS_FILE,
            backupDir.resolve("students.csv"),
            StandardCopyOption.REPLACE_EXISTING);
        Files.copy(GRADES_FILE,
            backupDir.resolve("grades.csv"),
            StandardCopyOption.REPLACE_EXISTING);

        System.out.println("Backup created: " + backupDir);
    }

    public static void main(String[] args) {
        try {
            // Initialize
            initialize();

            // Add students
            addStudent("STU001", "John Smith", "3A");
            addStudent("STU002", "Laura White", "3A");
            addStudent("STU003", "Joseph Green", "3B");

            // Record grades
            recordGrade("STU001", "Mathematics", 85);
            recordGrade("STU001", "English", 82);
            recordGrade("STU001", "Computer Science", 92);
            recordGrade("STU002", "Mathematics", 92);
            recordGrade("STU002", "English", 88);

            // Print report
            printStudents();
            printStudentGrades("STU001");

            // Calculate average
            double average = calculateAverage("STU001");
            System.out.printf("%nAverage STU001: %.2f%n", average);

            // Backup
            backupData();

        } catch (IOException e) {
            System.err.println("I/O Error: " + e.getMessage());
        }
    }
}

Best Practices

Rules for Efficient I/O

  • Use try-with-resources: Guaranteed automatic closing
  • Use BufferedReader/Writer: Better performance
  • Prefer NIO.2: More modern and clean API
  • Handle exceptions: IOException is very common
  • Specify encoding: Use StandardCharsets.UTF_8
  • Use Files.lines() for large files: Lazy stream
  • Always close streams: Avoid memory leaks
  • Check existence before operating: Files.exists()

Conclusion

I/O in Java provides powerful tools for working with files and streams. The NIO.2 API is the recommended approach for modern code.

Key Points to Remember

  • java.io: Classic streams, bytes and characters
  • BufferedReader/Writer: Better performance for text
  • NIO.2 Path: Modern path representation
  • NIO.2 Files: Static utilities for common operations
  • Serializable: For saving Java objects
  • StandardOpenOption: Precise control over file opening

In the next article we'll explore functional programming in Java: lambdas, Stream API and Optional.

Frequently asked questions

What are the two main I/O APIs offered by Java?

The two main I/O APIs in Java are package (stream-based) and New I/O (buffer-based).

Which API is recommended for modern code in Java?

NIO.2 API is the recommended approach for modern code.

What does Serialization allow you to do with Java objects?

Serialization allows you to save Java objects to files and read them back later.

When was the NIO.2 API introduced in Java?

The NIO.2 API was introduced in Java 7.

Sources and further reading

Try These Tools

Free developer tools related to this topic.

Commenti

Caricamento commenti...

Sign in to comment

Discuss this article in the community

Join the Telegram group to discuss with other developers, ask questions and share your experiences.

Did you like this article?

Explore other content on the blog or discover my projects