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
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
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
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
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
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"));
}
}
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
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.
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);
}
}
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
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.







