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
| Method | Pros | Cons | Best Use Case |
|---|---|---|---|
Files.readString() | Most concise | Risk of OOM on large files | Config files, small text |
Files.readAllLines() | Returns List directly | Loads entire file to memory | Medium files, line processing |
Files.lines() | Lazy, stream-based | Requires try-with-resources | Large log analysis |
BufferedReader | Traditional, fine-grained | Verbose | Cross-environment compatibility |
BufferedInputStream | Binary data | Inconvenient for text | Images, PDFs |
Files.copy() | One-line copy | No transformation | Simple 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
-
Always use try-with-resources: Missing
close()leaks OS file handles, locking files. -
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") -
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) -
Binary files — use NIO:
Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING); // simple copy
byte[] data = Files.readAllBytes(path); // all bytes at once -
Check file existence before accessing:
Path path = Path.of("data.txt");
if (Files.notExists(path)) {
Files.createFile(path);
}