Skip to main content

Ch 15.6 Mastering File Read and Write

Learn how to read and write files from beginner to production level. Java provides different classes for text files and binary files.


1. Java File I/O Class Map

        ┌───────────────────────────────────────────┐
│ Java File I/O │
├─────────────────────┬─────────────────────┤
│ Text-based │ Binary-based │
│ (Reader/Writer) │ (Stream) │
├─────────────────────┼─────────────────────┤
│ FileReader │ FileInputStream │
│ FileWriter │ FileOutputStream │
│ BufferedReader │ BufferedInputStream │
│ BufferedWriter │ BufferedOutputStream │
│ PrintWriter │ DataInputStream │
│ InputStreamReader │ DataOutputStream │
└─────────────────────┴─────────────────────┘
↓ Java 7+ Recommended ↓
java.nio.file.Files API
(Files.readString / writeString / lines / ...)

2. Reading Text Files

Method 1: BufferedReader + FileReader (Classic)

import java.io.*;

public class TextReadBasic {
public static void main(String[] args) {
// try-with-resources: automatically calls close() at block exit
try (BufferedReader br = new BufferedReader(new FileReader("hello.txt"))) {

String line;
int lineNum = 1;
while ((line = br.readLine()) != null) { // null = end of file
System.out.printf("%3d: %s%n", lineNum++, line);
}

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

Method 2: Explicit Encoding (Essential for Non-ASCII Text)

FileReader uses the JVM default encoding, which varies by OS. Always specify UTF-8 to avoid character corruption.

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

public class TextReadEncoding {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream("text.txt"),
StandardCharsets.UTF_8 // explicit encoding
))) {

br.lines() // returns Stream<String>
.filter(line -> !line.isBlank()) // skip blank lines
.map(String::trim) // strip leading/trailing spaces
.forEach(System.out::println);

} catch (IOException e) {
e.printStackTrace();
}
}
}

Method 3: NIO Files (Java 11+, Most Concise)

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

public class TextReadNIO {
public static void main(String[] args) throws IOException {
Path path = Path.of("hello.txt");

// Read entire file as a String (suitable for small files)
String content = Files.readString(path);
System.out.println(content);

// Read all lines as a List (medium-sized files)
List<String> lines = Files.readAllLines(path);
lines.forEach(System.out::println);

// Lazy streaming for large files (must use try-with-resources!)
try (var stream = Files.lines(path)) {
stream
.filter(line -> line.contains("ERROR"))
.forEach(System.out::println);
}
}
}

3. Writing Text Files

Method 1: BufferedWriter + FileWriter

import java.io.*;

public class TextWriteBasic {
public static void main(String[] args) {
// FileWriter(filename, append): false (default) = overwrite, true = append
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt", false))) {

bw.write("First line");
bw.newLine(); // OS-appropriate line separator
bw.write("Second line");
bw.newLine();
bw.write(String.format("Third line: number=%d, decimal=%.2f", 42, 3.14));

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

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

Method 2: PrintWriter (printf/println Support)

import java.io.*;

public class TextWritePrintWriter {
public static void main(String[] args) throws IOException {
// PrintWriter: convenient System.out.println-style writing to a file
try (PrintWriter pw = new PrintWriter(
new BufferedWriter(new FileWriter("report.txt")))) {

pw.println("=== Report ===");
pw.printf("Date: %s%n", java.time.LocalDate.now());
pw.printf("Items: %d%n", 5);
pw.println();
pw.println("Content goes here...");

// flush() not needed — try-with-resources handles it
}
}
}

Method 3: NIO Files (Java 11+)

import java.nio.file.*;

// Write a string (one-liner)
Files.writeString(Path.of("output.txt"), "Full file content");

// Write multiple lines
Files.writeString(Path.of("output.txt"),
"Line one\nLine two\nLine three");

// Text block (Java 15+)
String html = """
<html>
<body>
<h1>Hello!</h1>
</body>
</html>
""";
Files.writeString(Path.of("index.html"), html);

// Append mode
Files.writeString(Path.of("log.txt"), "New entry\n", StandardOpenOption.APPEND);

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

4. Binary File Read/Write

Images, PDFs, ZIP archives, and videos are binary data — use byte-based streams.

File Copy

import java.io.*;

public class BinaryFileCopy {
public static void main(String[] args) {
String src = "photo.jpg";
String dst = "photo_backup.jpg";

try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dst))
) {
byte[] buffer = new byte[8192]; // 8 KB buffer (optimal performance)
int bytesRead;
long totalBytes = 0;

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

System.out.printf("Copy complete: %s -> %s (%,d bytes)%n", src, dst, totalBytes);

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

Simpler with NIO:

Files.copy(Path.of("photo.jpg"), Path.of("photo_backup.jpg"),
StandardCopyOption.REPLACE_EXISTING);

5. Reading and Writing Method Comparison

MethodProsConsBest Use Case
Files.readString()Most conciseRisk of OOM on large filesConfig files, small text
Files.readAllLines()Returns List directlyLoads entire file to memoryMedium files, line processing
Files.lines()Lazy, stream-basedRequires try-with-resourcesLarge log analysis
BufferedReaderTraditional, fine-grainedVerboseCross-environment compatibility
BufferedInputStreamBinary dataInconvenient for textImages, PDFs
Files.copy()One-line copyNo transformationSimple copy/move

6. Practical Example 1: CSV File Processor

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

public class CsvProcessor {

record Student(String name, int english, int math, int science) {
int total() { return english + math + science; }
double avg() { return total() / 3.0; }
String grade() {
return switch ((int) avg() / 10) {
case 10, 9 -> "A";
case 8 -> "B";
case 7 -> "C";
case 6 -> "D";
default -> "F";
};
}
}

public static void main(String[] args) throws IOException {

// 1. Create sample CSV file
String csv = """
Name,English,Math,Science
Alice,85,90,88
Bob,72,65,80
Carol,95,98,92
David,60,70,55
Eve,88,82,91
""";
Files.writeString(Path.of("students.csv"), csv);
System.out.println("students.csv created.");

// 2. Parse CSV
List<Student> students;
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream("students.csv"),
java.nio.charset.StandardCharsets.UTF_8))) {
students = br.lines()
.skip(1) // skip header
.filter(line -> !line.isBlank())
.map(line -> {
String[] parts = line.split(",");
return new Student(
parts[0].trim(),
Integer.parseInt(parts[1].trim()),
Integer.parseInt(parts[2].trim()),
Integer.parseInt(parts[3].trim())
);
})
.collect(Collectors.toList());
}

// 3. Analysis
DoubleSummaryStatistics stats = students.stream()
.mapToDouble(Student::avg)
.summaryStatistics();

Student top = students.stream()
.max(Comparator.comparingDouble(Student::avg)).orElseThrow();
Student last = students.stream()
.min(Comparator.comparingDouble(Student::avg)).orElseThrow();

Map<String, List<Student>> byGrade = students.stream()
.collect(Collectors.groupingBy(Student::grade));

// 4. Save results
try (PrintWriter pw = new PrintWriter(
new BufferedWriter(new OutputStreamWriter(
new FileOutputStream("result.txt"),
java.nio.charset.StandardCharsets.UTF_8)))) {

pw.println("╔══════════════════════════════╗");
pw.println("║ Grade Report ║");
pw.println("╚══════════════════════════════╝");
pw.println();

pw.println("[ Overall Statistics ]");
pw.printf(" Students : %d%n", students.size());
pw.printf(" Avg score : %.1f%n", stats.getAverage());
pw.printf(" Top student : %s (%.1f)%n", top.name(), top.avg());
pw.printf(" Bottom : %s (%.1f)%n", last.name(), last.avg());
pw.println();

pw.println("[ Individual Scores ]");
pw.printf("%-8s %7s %5s %8s %6s %7s %5s%n",
"Name", "English", "Math", "Science", "Total", "Avg", "Grade");
pw.println("─".repeat(50));
students.stream()
.sorted(Comparator.comparingDouble(Student::avg).reversed())
.forEach(s -> pw.printf("%-8s %7d %5d %8d %6d %7.1f %5s%n",
s.name(), s.english(), s.math(), s.science(),
s.total(), s.avg(), s.grade()));
pw.println();

pw.println("[ By Grade ]");
byGrade.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(e -> {
String names = e.getValue().stream()
.map(Student::name)
.collect(Collectors.joining(", "));
pw.printf(" Grade %s: %s%n", e.getKey(), names);
});
}

System.out.println("Results saved to result.txt");
System.out.println(Files.readString(Path.of("result.txt")));
}
}

7. Practical Example 2: Log File Analyzer

import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;
import java.util.regex.*;

public class LogAnalyzer {

record LogEntry(String timestamp, String level, String message) {}

static void generateSampleLog(Path path) throws IOException {
List<String> logs = List.of(
"2024-03-15 10:01:23 [INFO] Server started",
"2024-03-15 10:01:25 [INFO] Database connection established",
"2024-03-15 10:02:11 [INFO] User login: user001",
"2024-03-15 10:03:45 [WARN] Memory usage above 75%",
"2024-03-15 10:04:12 [ERROR] DB query timeout: SELECT * FROM orders",
"2024-03-15 10:04:13 [INFO] Retrying...",
"2024-03-15 10:04:15 [INFO] Query retry succeeded",
"2024-03-15 10:05:30 [WARN] Memory usage above 85%",
"2024-03-15 10:06:00 [ERROR] External API connection failed: payment-service",
"2024-03-15 10:06:01 [ERROR] Payment processing failed: orderId=12345",
"2024-03-15 10:07:00 [INFO] User logout: user001",
"2024-03-15 10:08:00 [WARN] Memory usage above 90%",
"2024-03-15 10:09:00 [ERROR] OutOfMemoryError occurred"
);
Files.write(path, logs);
}

static Optional<LogEntry> parseLine(String line) {
Pattern p = Pattern.compile(
"(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}) \\[(\\w+)\\s*\\] (.+)");
Matcher m = p.matcher(line);
if (m.matches()) {
return Optional.of(new LogEntry(m.group(1), m.group(2).trim(), m.group(3).trim()));
}
return Optional.empty();
}

public static void main(String[] args) throws IOException {
Path logPath = Path.of("app.log");
Path reportPath = Path.of("log_report.txt");

generateSampleLog(logPath);
System.out.println("app.log created (" + Files.size(logPath) + " bytes)");

// Stream large file lazily
List<LogEntry> entries;
try (Stream<String> lines = Files.lines(logPath)) {
entries = lines
.map(LogAnalyzer::parseLine)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}

Map<String, Long> countByLevel = entries.stream()
.collect(Collectors.groupingBy(LogEntry::level, Collectors.counting()));

List<LogEntry> errors = entries.stream()
.filter(e -> "ERROR".equals(e.level())).collect(Collectors.toList());
List<LogEntry> warnings = entries.stream()
.filter(e -> "WARN".equals(e.level())).collect(Collectors.toList());

try (PrintWriter pw = new PrintWriter(
new BufferedWriter(new FileWriter(reportPath.toFile())))) {

pw.printf("=== Log Analysis Report ===%n");
pw.printf("File: %s%n", logPath);
pw.printf("Total entries: %d%n%n", entries.size());

pw.println("[ Level Statistics ]");
countByLevel.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.forEach(e -> pw.printf(" %-6s: %d%n", e.getKey(), e.getValue()));

pw.printf("%n[ ERROR entries (%d) ]%n", errors.size());
errors.forEach(e -> pw.printf(" %s - %s%n", e.timestamp(), e.message()));

pw.printf("%n[ WARN entries (%d) ]%n", warnings.size());
warnings.forEach(e -> pw.printf(" %s - %s%n", e.timestamp(), e.message()));
}

System.out.println("Report saved: " + reportPath);
System.out.println(Files.readString(reportPath));
}
}

8. Practical Example 3: Properties File Read/Write

.properties files are the Java standard for application configuration.

import java.io.*;
import java.util.Properties;

public class PropertiesExample {
public static void main(String[] args) throws IOException {

// 1. Write a properties file
Properties writeProps = new Properties();
writeProps.setProperty("db.host", "localhost");
writeProps.setProperty("db.port", "3306");
writeProps.setProperty("db.name", "myapp");
writeProps.setProperty("db.user", "admin");
writeProps.setProperty("app.version", "1.0.0");
writeProps.setProperty("app.debug", "false");

try (BufferedWriter bw = new BufferedWriter(new FileWriter("config.properties"))) {
writeProps.store(bw, "Application Configuration");
}
System.out.println("config.properties created.");

// 2. Read the properties file
Properties props = new Properties();
try (BufferedReader br = new BufferedReader(new FileReader("config.properties"))) {
props.load(br);
}

String host = props.getProperty("db.host");
int port = Integer.parseInt(props.getProperty("db.port"));
String dbName = props.getProperty("db.name");
boolean debug = Boolean.parseBoolean(props.getProperty("app.debug"));
String version = props.getProperty("app.version", "0.0.1"); // fallback if missing

System.out.printf("DB: %s:%d/%s%n", host, port, dbName);
System.out.printf("Version: %s, Debug: %b%n", version, debug);

// 3. Update and save
props.setProperty("app.debug", "true");
props.setProperty("app.version", "1.0.1");
try (BufferedWriter bw = new BufferedWriter(new FileWriter("config.properties"))) {
props.store(bw, "Updated Configuration");
}
System.out.println("Configuration updated.");
}
}

Pro Tips — File I/O Checklist
  1. Always use try-with-resources: Missing close() leaks OS file handles, locking files.

  2. Always specify encoding for text files:

    // Correct
    new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8)
    new OutputStreamWriter(new FileOutputStream("file.txt"), StandardCharsets.UTF_8)

    // Dangerous (OS default encoding varies by platform)
    new FileReader("file.txt")
    new FileWriter("file.txt")
  3. Choose strategy by file size:

    Small (< a few MB):  Files.readString() / writeString()
    Medium (< hundreds MB): Files.readAllLines() or BufferedReader
    Large (GB+): Files.lines() + Stream (lazy reading)
  4. Binary files — use NIO:

    Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING); // simple copy
    byte[] data = Files.readAllBytes(path); // all bytes at once
  5. Check file existence before accessing:

    Path path = Path.of("data.txt");
    if (Files.notExists(path)) {
    Files.createFile(path);
    }