15.6 Mastering File Read & Write
Learn how to read and write files from scratch to production level. Java provides different classes for text files and binary files.
1. File I/O Class Map
┌───────────────────────────────────────────┐
│ Java File I/O │
├─────────────────────┬─────────────────────┤
│ Text-based │ Binary-based │
│ (Reader/Writer) │ (InputStream/OutputStream) │
├─────────────────────┼─────────────────────┤
│ 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: close() is called automatically when the block ends
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("Line %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 Production)
FileReader uses the JVM default encoding. Since encoding varies by server/OS, always specify UTF-8 explicitly to avoid garbled characters.
import java.io.*;
import java.nio.charset.StandardCharsets;
public class TextReadEncoding {
public static void main(String[] args) {
// Specify encoding via InputStreamReader
try (BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream("data.txt"),
StandardCharsets.UTF_8 // explicit UTF-8
))) {
br.lines() // convert to Stream<String>
.filter(line -> !line.isBlank()) // skip blank lines
.map(String::trim) // strip leading/trailing whitespace
.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");
// Entire file as one String (suitable for small files)
String content = Files.readString(path);
System.out.println(content);
// All lines as a List (medium-sized files)
List<String> lines = Files.readAllLines(path);
lines.forEach(System.out::println);
// Lazy streaming (suitable 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 break (\n or \r\n)
bw.write("Second line");
bw.newLine();
bw.write(String.format("Third line: number=%d, float=%.2f", 42, 3.14));
System.out.println("File write complete");
} catch (IOException e) {
System.out.println("Write error: " + e.getMessage());
}
}
}
Method 2: PrintWriter (supports printf/println)
import java.io.*;
public class TextWritePrintWriter {
public static void main(String[] args) throws IOException {
// PrintWriter: convenient like System.out.println
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("Item count: %d%n", 5);
pw.println();
pw.println("Content goes here...");
// Flushed automatically by try-with-resources
}
}
}
Method 3: NIO Files (Java 11+)
import java.nio.file.*;
// Write in one line (most concise)
Files.writeString(Path.of("output.txt"), "Entire file content");
// Multiple lines
Files.writeString(Path.of("output.txt"),
"Line 1\nLine 2\nLine 3");
// With 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"), "Additional content\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, ZIPs, videos, and other binary data require 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]; // 8KB 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
// Copy a file in one line!
Files.copy(Path.of("photo.jpg"), Path.of("photo_backup.jpg"),
StandardCopyOption.REPLACE_EXISTING);
5. Comparison Table
| Method | Pros | Cons | Best For |
|---|---|---|---|
Files.readString() | Most concise | Risky for large files (OOM) | Config files, small text |
Files.readAllLines() | Returns a List directly | Loads entire file into memory | Medium files, line-by-line processing |
Files.lines() | Lazy reading, stream-based | Must use try-with-resources | Large log analysis |
BufferedReader | Traditional, fine-grained control | Verbose code | Cross-environment compatibility |
BufferedInputStream | Binary data | Inconvenient for text conversion | Images, PDFs, etc. |
Files.copy() | One-line copy | No transformation | Simple file copy/move |
6. Real-World 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 korean, int math, int english) {
int total() { return korean + math + english; }
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,korean,math,english
Alice,85,90,88
Bob,72,65,80
Carol,95,98,92
Dave,60,70,55
Eve,88,82,91
""";
Files.writeString(Path.of("students.csv"), csv);
System.out.println("students.csv created\n");
// 2. Read and 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. Statistical 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 to result.txt
try (PrintWriter pw = new PrintWriter(
new BufferedWriter(new OutputStreamWriter(
new FileOutputStream("result.txt"),
java.nio.charset.StandardCharsets.UTF_8)))) {
pw.println("╔══════════════════════════════╗");
pw.println("║ Grade Analysis 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 : %s (%.1f)%n", top.name(), top.avg());
pw.printf(" Bottom : %s (%.1f)%n", last.name(), last.avg());
pw.println();
pw.println("[ Individual Report ]");
pw.printf("%-8s %6s %4s %7s %5s %6s %5s%n",
"Name", "Korean", "Math", "English", "Total", "Avg", "Grade");
pw.println("─".repeat(50));
students.stream()
.sorted(Comparator.comparingDouble(Student::avg).reversed())
.forEach(s -> pw.printf("%-8s %6d %4d %7d %5d %6.1f %5s%n",
s.name(), s.korean(), s.math(), s.english(),
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\n");
// 5. Print the saved file
System.out.println(Files.readString(Path.of("result.txt")));
}
}
[result.txt output]
╔══════════════════════════════╗
║ Grade Analysis Report ║
╚══════════════════════════════╝
[ Overall Statistics ]
Students : 5
Avg Score : 80.5
Top : Carol (95.0)
Bottom : Dave (61.7)
[ Individual Report ]
Name Korean Math English Total Avg Grade
──────────────────────────────────────────────────
Carol 95 98 92 285 95.0 A
Eve 88 82 91 261 87.0 B
Alice 85 90 88 263 87.7 B
Bob 72 65 80 217 72.3 C
Dave 60 70 55 185 61.7 D
[ By Grade ]
Grade A: Carol
Grade B: Eve, Alice
Grade C: Bob
Grade D: Dave
7. Real-World Example 2: Log File Analyzer
A production pattern for searching large log files for specific patterns.
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) {}
// Generate sample log file
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 connected",
"2024-03-15 10:02:11 [INFO] User login: user001",
"2024-03-15 10:03:45 [WARN] Memory usage over 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 over 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 over 90%",
"2024-03-15 10:09:00 [ERROR] OutOfMemoryError occurred"
);
Files.write(path, logs);
}
// Parse log line using regex
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)\n");
// Lazy stream reading for large files
List<LogEntry> entries;
try (Stream<String> lines = Files.lines(logPath)) {
entries = lines
.map(LogAnalyzer::parseLine)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
// Analysis
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());
// Save report
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 lines: %d%n%n", entries.size());
pw.println("[ By Level ]");
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 List (%d) ]%n", errors.size());
errors.forEach(e ->
pw.printf(" %s - %s%n", e.timestamp(), e.message()));
pw.printf("%n[ WARN List (%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));
}
}
[log_report.txt output]
=== Log Analysis Report ===
File: app.log
Total lines: 13
[ By Level ]
INFO : 6
ERROR : 4
WARN : 3
[ ERROR List (4) ]
2024-03-15 10:04:12 - DB query timeout: SELECT * FROM orders
2024-03-15 10:06:00 - External API connection failed: payment-service
2024-03-15 10:06:01 - Payment processing failed: orderId=12345
2024-03-15 10:09:00 - OutOfMemoryError occurred
[ WARN List (3) ]
2024-03-15 10:03:45 - Memory usage over 75%
2024-03-15 10:05:30 - Memory usage over 85%
2024-03-15 10:08:00 - Memory usage over 90%
8. Real-World Example 3: Properties File Read/Write
.properties files are standard for Java application configuration.
import java.io.*;
import java.util.Properties;
public class PropertiesExample {
public static void main(String[] args) throws IOException {
// 1. Write config 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"); // comment included
}
System.out.println("config.properties created");
// 2. Read config 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 default
System.out.printf("DB: %s:%d/%s%n", host, port, dbName);
System.out.printf("App 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("Config updated");
}
}
[config.properties content]
#Application Configuration
#Fri Mar 15 10:00:00 UTC 2024
db.host=localhost
db.port=3306
db.name=myapp
db.user=admin
app.version=1.0.0
app.debug=false
File I/O Production Checklist:
-
Always use try-with-resources: Forgetting
close()leaks file handles → the OS may lock the file. -
Always specify encoding:
// Correct
new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8)
new OutputStreamWriter(new FileOutputStream("file.txt"), StandardCharsets.UTF_8)
// Risky (uses OS default encoding — varies by server)
new FileReader("file.txt")
new FileWriter("file.txt") -
Choose strategy by file size:
Small (< few MB): Files.readString() / writeString()
Medium (hundreds MB): Files.readAllLines() or BufferedReader
Large (GB+): Files.lines() + Stream (lazy reading) -
Use NIO for binary files:
Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING); // simple copy
byte[] data = Files.readAllBytes(path); // as byte array -
Check file existence first:
Path path = Path.of("data.txt");
if (Files.notExists(path)) {
Files.createFile(path); // create if absent
}
// FileWriter's second argument (append) creates the file automatically if missing