Senior 5 min · March 05, 2026

Java FileWriter overwrites logs on restart - append mode

FileWriter's default mode overwrites files silently on JVM restart, causing log loss.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • FileReader and FileWriter are character streams for reading/writing text files in Java.
  • FileWriter overwrites by default; pass true for append mode to avoid silent data loss.
  • Always wrap with BufferedReader/BufferedWriter — raw streams make one OS syscall per character.
  • Platform default charset can corrupt non-ASCII data; use InputStreamReader/OutputStreamWriter with UTF-8.
  • Always use try-with-resources to guarantee flush and close — missing close loses data silently.
  • Use newLine() for platform-independent line separators, not hardcoded "\n".
Plain-English First

Imagine your Java program is a person sitting at a desk. FileReader is like that person picking up a physical letter from a folder and reading it word by word. FileWriter is like that same person picking up a pen and writing a new letter into a folder. The 'file' on disk is the folder, and your Java program is the person doing the reading or writing. Simple as that.

Every serious application eventually needs to talk to the file system — reading config files, writing logs, importing CSV data, exporting reports. Java's FileReader and FileWriter are the most direct tools for doing exactly that with text files. They're part of the java.io package, which has been in Java since version 1.1, and understanding them properly unlocks a whole layer of practical programming that goes beyond printing to the console.

The problem they solve is straightforward: your program's memory is temporary. The moment your JVM shuts down, everything in RAM is gone. FileWriter lets you persist text data to disk so it survives restarts, reboots, and crashes. FileReader is the flip side — it lets you pull that saved data back into memory so your program can work with it again. Together they form the foundation of text-based file I/O in Java.

By the end of this article you'll know how FileReader and FileWriter work under the hood, how to use them safely with try-with-resources, how to append instead of overwrite, how to read files efficiently character by character or line by line, and exactly which real-world situations call for them versus their more powerful alternatives. You'll also know the three mistakes that trip up most intermediate developers — and how to avoid them entirely.

What FileWriter Actually Does — Writing Text to Disk

FileWriter is a character stream writer. That means it converts Java characters (which are Unicode) into bytes and writes them to a file. By default it uses the platform's default charset — more on why that matters in the pitfalls section.

When you create a FileWriter with just a filename, it opens the file in 'overwrite' mode. Every run wipes the file clean and starts fresh. If you pass true as the second argument, it switches to 'append' mode — new content goes to the end of the existing file. This is how log files work in most basic applications.

FileWriter extends OutputStreamWriter, which extends Writer. So it's fully polymorphic — anywhere you need a Writer, a FileWriter fits. This matters because it means you can wrap it with a BufferedWriter for dramatically better performance. Raw FileWriter hits the disk on every single write() call. BufferedWriter batches those writes into chunks. For anything longer than a few lines, always wrap.

You must close a FileWriter when you're done. Failing to do so is one of the most common bugs — the data never actually reaches the disk because it's still sitting in an internal buffer waiting to be flushed. The safest way to guarantee the file gets closed is try-with-resources, which Java handles automatically.

WriteUserReport.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class WriteUserReport {

    public static void main(String[] args) {

        String reportFilePath = "user_report.txt";

        // Try-with-resources ensures the writer is closed automatically,
        // even if an exception is thrown mid-write.
        // BufferedWriter wraps FileWriter for performance — without it,
        // every write() call is a separate OS-level disk operation.
        try (BufferedWriter reportWriter = new BufferedWriter(new FileWriter(reportFilePath))) {

            // Write the report header
            reportWriter.write("=== Monthly User Report ===");
            reportWriter.newLine(); // platform-safe newline (\r\n on Windows, \n on Unix)

            reportWriter.write("Username: alice_dev");
            reportWriter.newLine();

            reportWriter.write("Logins this month: 47");
            reportWriter.newLine();

            reportWriter.write("Status: Active");
            reportWriter.newLine();

            // No need to call flush() or close() — try-with-resources does it
            System.out.println("Report written successfully to: " + reportFilePath);

        } catch (IOException writeException) {
            // IOException covers: file not found, permission denied, disk full, etc.
            System.err.println("Failed to write report: " + writeException.getMessage());
        }
    }
}
Output
Report written successfully to: user_report.txt
[user_report.txt contents]
=== Monthly User Report ===
Username: alice_dev
Logins this month: 47
Status: Active
Pro Tip: Always Use newLine() Over "\n"
Hardcoding "\n" in a FileWriter will produce Unix-style line endings on every platform — which breaks files opened in Notepad on Windows. BufferedWriter.newLine() writes the correct line separator for whatever OS is running. Use it every time.
Production Insight
Without BufferedWriter, each write() call is a separate disk write.
For a 10KB file, that's thousands of syscalls vs one chunk.
In production, raw FileWriter can cause 100x slowdowns on high-throughput logging.
Rule: always wrap FileWriter in BufferedWriter unless you're writing a single small line.
Key Takeaway
Always wrap FileWriter in BufferedWriter.
Raw FileWriter hits disk per character — unacceptable for production.
Use try-with-resources to guarantee flush and close.
Choosing Between Raw FileWriter and BufferedWriter
IfWriting less than 100 characters total
UseRaw FileWriter works, but wrapping adds negligible overhead. Wrap anyway for consistency.
IfWriting more than 100 characters or multiple lines
UseAlways use BufferedWriter. Performance difference grows with file size.
IfWriting to a slow filesystem (NFS, network drive)
UseBufferedWriter is mandatory — reduce syscalls to avoid latency spikes.

Appending to a File — The Second Argument That Changes Everything

The most common gotcha with FileWriter is accidentally nuking an existing file. If you're building a logger, an audit trail, or any kind of running history, you need append mode. The fix is a single boolean argument: new FileWriter(filePath, true). That true tells Java to open the file at the end rather than from the beginning.

Under the hood, true maps to the FileOutputStream append flag, which maps to the OS-level open call with O_APPEND. This means even if two processes try to append to the same file simultaneously, the OS handles the ordering — though for true concurrent logging in production you'd use a dedicated logging framework.

The pattern below simulates a simple application event log — each time the program runs, it adds a new timestamped entry without touching anything already in the file. This is exactly how application logs, audit trails, and event histories are built at a basic level.

AppendEventLog.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class AppendEventLog {

    // Centralized log path — in a real app this comes from config
    private static final String LOG_FILE_PATH = "application_events.log";
    private static final DateTimeFormatter TIMESTAMP_FORMAT =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static void logEvent(String eventMessage) throws IOException {
        // The 'true' argument is the key — it enables append mode.
        // Without it, every call to logEvent() would destroy the previous log.
        try (BufferedWriter logWriter = new BufferedWriter(new FileWriter(LOG_FILE_PATH, true))) {

            String timestamp = LocalDateTime.now().format(TIMESTAMP_FORMAT);

            // Format: [2024-06-15 14:32:01] User alice_dev logged in
            logWriter.write("[" + timestamp + "] " + eventMessage);
            logWriter.newLine();
        }
        // Writer is closed here — the OS commits this line to disk
    }

    public static void main(String[] args) throws IOException {
        // Simulate three events happening across one session
        logEvent("Application started");
        logEvent("User alice_dev logged in");
        logEvent("User alice_dev exported report");

        System.out.println("Events logged. Check: " + LOG_FILE_PATH);
    }
}
Output
Events logged. Check: application_events.log
[application_events.log contents after first run]
[2024-06-15 14:32:01] Application started
[2024-06-15 14:32:01] User alice_dev logged in
[2024-06-15 14:32:01] User alice_dev exported report
[application_events.log contents after second run — old entries still there]
[2024-06-15 14:32:01] Application started
[2024-06-15 14:32:01] User alice_dev logged in
[2024-06-15 14:32:01] User alice_dev exported report
[2024-06-15 14:35:44] Application started
[2024-06-15 14:35:44] User alice_dev logged in
[2024-06-15 14:35:44] User alice_dev exported report
Watch Out: Forgetting the Append Flag Deletes Your Data
new FileWriter("mylog.log") with no second argument will silently erase your entire log file every time the program starts. This is a zero-error, zero-warning data loss bug. Always be explicit: pass true for append, or false for overwrite — never rely on the default when it matters.
Production Insight
Append flag is atomic at OS level — two concurrent writers won't corrupt each other's data.
But if one writer opens without append, it truncates the file and the other writer loses its content.
Rule: enforce append flag in code review and config validation.
Key Takeaway
Mixing up append and overwrite causes silent data loss.
Explicitly pass true or false — never rely on default.
For logs, audit trails, and history: append mode is your friend.
When to Use Append vs Overwrite
IfWriting log entries, audit trails, event streams
UseAlways use append mode: new FileWriter(path, true).
IfExporting a new report each time (e.g., daily CSV dump)
UseOverwrite mode is correct. Use new FileWriter(path, false) or default.
IfRunning in a container with ephemeral storage
UseStill use append if you want multiple runs preserved; but consider volume mounting.

Reading Files With FileReader — Character by Character and Line by Line

FileReader is the reading counterpart. Like FileWriter, it's a character stream — it reads bytes from disk and converts them to Java chars using the platform's default charset. Wrapping it with BufferedReader is not optional for real code. BufferedReader adds a read buffer (8KB by default) so Java isn't making a system call to the OS for every single character, and it provides the essential readLine() method.

readLine() returns the next line of text without the line terminator, or null when the file ends. That null check in the while loop is the idiomatic Java pattern for reading a file line by line. Forgetting that null signals EOF (end of file) and not an error is a classic beginner mistake.

The example below reads a simple CSV-style config file — the kind you'd use to store database connection settings or feature flags. It parses each line into a key-value pair, demonstrating a real use case rather than just printing raw file contents.

ReadConfigFile.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class ReadConfigFile {

    // Assume this file exists on disk with the content shown in the output section
    private static final String CONFIG_FILE_PATH = "app_config.properties";

    public static Map<String, String> loadConfig(String filePath) throws IOException {
        Map<String, String> configSettings = new HashMap<>();

        // BufferedReader wraps FileReader — gives us readLine() and a memory buffer
        // so we're not hitting the disk character-by-character
        try (BufferedReader configReader = new BufferedReader(new FileReader(filePath))) {

            String currentLine;

            // readLine() returns null at end-of-file — NOT an empty string
            // This while loop is the standard Java pattern for reading all lines
            while ((currentLine = configReader.readLine()) != null) {

                // Skip blank lines and comment lines (lines starting with #)
                if (currentLine.isBlank() || currentLine.startsWith("#")) {
                    continue;
                }

                // Each line is expected to look like: db.host=localhost
                String[] keyValuePair = currentLine.split("=", 2);

                if (keyValuePair.length == 2) {
                    String settingKey = keyValuePair[0].trim();
                    String settingValue = keyValuePair[1].trim();
                    configSettings.put(settingKey, settingValue);
                }
            }
        }
        // BufferedReader (and the FileReader inside it) closed automatically here

        return configSettings;
    }

    public static void main(String[] args) {
        try {
            Map<String, String> appConfig = loadConfig(CONFIG_FILE_PATH);

            System.out.println("Loaded " + appConfig.size() + " config settings:");
            appConfig.forEach((key, value) ->
                System.out.println("  " + key + " -> " + value)
            );

        } catch (IOException configLoadException) {
            System.err.println("Could not load config: " + configLoadException.getMessage());
        }
    }
}
Output
[app_config.properties file contents]
# Database configuration
db.host=localhost
db.port=5432
db.name=forge_production
# Feature flags
feature.dark_mode=true
[Console output when running ReadConfigFile]
Loaded 4 config settings:
db.host -> localhost
db.port -> 5432
db.name -> forge_production
feature.dark_mode -> true
Interview Gold: Why Wrap FileReader With BufferedReader?
FileReader.read() makes one system call per character — that's potentially thousands of OS calls for a 10KB file. BufferedReader reads a chunk (8192 chars by default) into memory in one call, then serves your code from that buffer. This can be 10-100x faster on real hardware. This answer alone separates candidates who've actually used I/O from those who've just read about it.
Production Insight
readLine() returns null at EOF — not an empty string.
A common bug: using while ((line = reader.readLine()) != null) is correct, but many novices write while (!line.isEmpty()) and miss the last line.
Rule: always check for null, never treat empty string as EOF.
Key Takeaway
Always wrap FileReader in BufferedReader for performance and readLine().
readLine() returns null at EOF — never treat empty string as end.
For small files, consider Files.readString() (Java 11+).
Which Reading Method to Use?
IfFile is less than 100 lines and you need all content as a string
UseUse Files.readString(path) (Java 11+) — simpler, no looping.
IfFile is large ( > 1MB ) or you need to process line by line
UseUse BufferedReader wrapping FileReader — memory efficient.
IfYou need fine-grained control over reading (e.g., skip bytes)
UseUse InputStreamReader or NIO channels; FileReader is too high-level.

Copying a Text File — Putting FileReader and FileWriter Together

The clearest way to understand both classes working together is to build a file copy utility. This is also a surprisingly common real-world task — think copying templates, creating backup files, or duplicating config files before modifying them.

This example reads from a source file line by line and writes each line to a destination file, preserving the structure. It also adds a metadata header to the copy — something a raw Files.copy() call couldn't do without extra steps.

Notice the try-with-resources block manages both the reader and the writer simultaneously. When the block exits — successfully or via exception — both streams are closed in reverse declaration order (writer first, then reader). This is Java's guaranteed cleanup contract, and it's the only safe way to handle multiple I/O resources together.

TextFileCopier.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDate;

public class TextFileCopier {

    /**
     * Copies a text file from sourcePath to destinationPath,
     * prepending a metadata header to the copy.
     *
     * @param sourcePath      path to the original text file
     * @param destinationPath path where the copy will be written
     * @throws IOException if either file cannot be opened or written
     */
    public static void copyWithMetadata(String sourcePath, String destinationPath)
            throws IOException {

        // Both resources declared in one try-with-resources block —
        // Java closes them both automatically, in reverse order (writer, then reader)
        try (
            BufferedReader sourceReader = new BufferedReader(new FileReader(sourcePath));
            BufferedWriter destinationWriter = new BufferedWriter(new FileWriter(destinationPath))
        ) {
            // Add a metadata header that doesn't exist in the original
            destinationWriter.write("# Copied from: " + sourcePath);
            destinationWriter.newLine();
            destinationWriter.write("# Copy date: " + LocalDate.now());
            destinationWriter.newLine();
            destinationWriter.write("# ----------------------------------------");
            destinationWriter.newLine();

            String sourceLine;
            int lineCount = 0;

            // Read source line by line — null signals we've reached the end
            while ((sourceLine = sourceReader.readLine()) != null) {
                destinationWriter.write(sourceLine);
                destinationWriter.newLine();
                lineCount++;
            }

            System.out.println("Copy complete. Lines copied: " + lineCount);
            System.out.println("Destination: " + destinationPath);
        }
        // Both streams flushed and closed here — content is safely on disk
    }

    public static void main(String[] args) {
        try {
            copyWithMetadata("original_template.txt", "template_backup_copy.txt");
        } catch (IOException copyException) {
            System.err.println("File copy failed: " + copyException.getMessage());
        }
    }
}
Output
Copy complete. Lines copied: 12
Destination: template_backup_copy.txt
[template_backup_copy.txt first 3 lines]
# Copied from: original_template.txt
# Copy date: 2024-06-15
# ----------------------------------------
[... rest of original file content follows ...]
Pro Tip: Multiple Resources in One Try-With-Resources
Separate multiple AutoCloseable resources with semicolons inside the try() parentheses. They close in reverse declaration order — always. This means if you have a reader feeding a writer, the writer closes first (flushing its buffer to disk) before the reader closes. Declare them in the order you open them and let Java handle the rest.
Production Insight
An IOException while reading after writing has started leaves a partial destination file.
The try-with-resources ensures the writer is closed (and flushed) even if the read fails.
But the partial file will exist. For critical copies, use a temp file + rename pattern.
Rule: for production file copies, write to a temp file first, then atomically rename.
Key Takeaway
Use try-with-resources for multiple streams — declared in open order, closed in reverse.
Writer closes first, flushing data to disk, then reader closes.
For production, prefer temp-file + rename for atomicity.
Copy Strategy Selection
IfSimple copy, no transformation needed
UseUse Files.copy() (NIO) — simpler, faster, built-in buffering.
IfNeed to add/remove/modify content during copy
UseUse BufferedReader/BufferedWriter pair as shown above.
IfCopying very large files ( > 1GB )
UseUse NIO channels (FileChannel.transferTo) for zero-copy performance.

Character Encoding Pitfalls — Why Your Non-ASCII Data Gets Corrupted

FileReader and FileWriter use the platform's default charset by default. On a US English Windows machine, that's usually windows-1252 or Cp1252. On a Linux server, it's often UTF-8. When you write a file with accented characters on your dev machine (windows-1252) and the file is read on a server (UTF-8), those characters become garbled — 'é' becomes 'é' or '?'.

This is the most invisible, hardest-to-debug bug in Java I/O because the code compiles, runs, and produces output — it's just the wrong output. No exception is thrown. The only way to detect it is to inspect the raw bytes or open the file on a different platform.

The fix is straightforward: never use raw FileReader/FileWriter when your content might contain non-ASCII characters. Instead, use InputStreamReader wrapping a FileInputStream, and OutputStreamWriter wrapping a FileOutputStream. Both accept an explicit charset parameter.

The example below shows how to write and read a file with UTF-8 encoding, guaranteeing the same result on any platform.

Utf8FileExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.io.*;
import java.nio.charset.StandardCharsets;

public class Utf8FileExample {

    public static void main(String[] args) throws IOException {
        String message = "Pièce de résistance — café au lait: €5,00 éñçödë";
        String filePath = "utf8_demo.txt";

        // Write using UTF-8 explicitly
        try (BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(filePath), StandardCharsets.UTF_8))) {
            writer.write(message);
            writer.newLine();
        }

        // Read using UTF-8 explicitly
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8))) {
            String line = reader.readLine();
            System.out.println("Read back: " + line);
            System.out.println("Characters match: " + message.equals(line));
        }
    }
}
Output
Read back: Pièce de résistance — café au lait: €5,00 éñçödë
Characters match: true
[If you run this on any OS, the output is identical.]
Critical: Raw FileReader/FileWriter Are Not Cross-Platform Safe
If your application runs on multiple OSes (developer laptops + Linux servers), raw FileReader/FileWriter will produce different byte streams on each. Always use InputStreamReader/OutputStreamWriter with an explicit charset like StandardCharsets.UTF_8 to guarantee consistency. This is the #1 cause of 'works on my machine' bugs in file I/O.
Production Insight
A service that processed French customer names (with accented characters) returned garbage after moving from dev Windows to prod Linux.
Root cause: FileReader used default charset — different on each platform.
Fix: switched to InputStreamReader with UTF-8. No more data corruption.
Rule: never trust default charset for files that leave the machine.
Key Takeaway
Default charset differs per platform — causes silent data corruption.
For any non-ASCII content, use InputStreamReader/OutputStreamWriter with UTF-8.
Test on all deployment platforms to catch charset bugs early.
Charset Selection for File I/O
IfFile contains only ASCII characters (A-Z, 0-9, basic symbols)
UseFileReader/FileWriter are safe — default charset won't affect ASCII.
IfFile contains accented characters, emojis, or any non-ASCII
UseUse InputStreamReader/OutputStreamWriter with explicit StandardCharsets.UTF_8.
IfFile needs to be compatible with legacy systems (e.g., ISO-8859-1)
UseUse explicit charset matching the legacy system, not the platform default.
● Production incidentPOST-MORTEMseverity: high

Production Incident: Log File Goes Missing After Server Restart

Symptom
After a routine nightly server restart, all new transaction log entries for the next 3 hours were written to a fresh file — the previous day's logs were silently deleted. Operations noticed when a compliance audit failed to find entries for that window.
Assumption
The team assumed that since the log writer was called daily, it would continue appending to the existing file. They had not reviewed the writer's constructor for years.
Root cause
The FileWriter was initialized as new FileWriter("transactions.log") without the second boolean parameter. This default mode overwrites the file every time the JVM starts. After the server restart, the old file contents were replaced with new data.
Fix
Changed to new FileWriter("transactions.log", true) to enable append mode. Also added a health check that validates the file still contains expected entries after restart.
Key lesson
  • Always explicitly specify append mode (true) for any persistent log, audit trail, or incremental output.
  • Treat file writer initialization as a configuration review item during release checklists.
  • Add monitoring to detect sudden file truncation — e.g., compare expected line count with actual.
Production debug guideCommon symptoms and diagnostic actions for file I/O failures.5 entries
Symptom · 01
File is empty or contains only partial data after write
Fix
Check if the FileWriter was closed. Use try-with-resources. If closed manually, ensure close() is in a finally block. Check disk space and permissions.
Symptom · 02
Previously written data is missing on next run
Fix
Verify FileWriter constructor: new FileWriter(path) overwrites. Use new FileWriter(path, true) for append. Check if a file deletion occurs elsewhere.
Symptom · 03
Non-ASCII characters appear as '?' or garbage
Fix
Confirm charset: FileReader/FileWriter use platform default. Rewrite with InputStreamReader/OutputStreamWriter specifying StandardCharsets.UTF_8.
Symptom · 04
Very slow read/write on files larger than a few KB
Fix
Check if BufferedReader/BufferedWriter is used. Raw reader/writer makes one OS syscall per character. Wrap immediately.
Symptom · 05
readLine() returns empty string for blank lines, but stops prematurely
Fix
Remember readLine() returns null at EOF, not an empty string. A blank line returns "". Ensure loop checks line != null, not !line.isEmpty().
★ Quick Debug Cheat Sheet: File I/O FailuresImmediate actions and commands when your file reads/writes misbehave.
Data not written to file
Immediate action
Check if FileWriter was closed. Check disk space using `df -h` (Linux) or `dir` (Windows). Verify file path existence.
Commands
ls -la /path/to/file (Linux) or dir C:\path\to\file (Windows) — check file size and permissions
cat /path/to/file (Linux) or type C:\path\to\file (Windows) — view file contents immediately
Fix now
Close the writer properly. If using try-with-resources, ensure the resource is declared inside the try(). For manual close, add finally block.
Garbled characters or '?' in output+
Immediate action
Check file encoding with `file -i filename` (Linux) or note charset mismatch. Stop using raw FileWriter.
Commands
file -i /path/to/file (Linux) — shows charset of written file
Check JVM default charset: System.out.println(Charset.defaultCharset())
Fix now
Replace FileWriter with OutputStreamWriter wrapped around FileOutputStream, specifying StandardCharsets.UTF_8. Same for reading: InputStreamReader + FileInputStream with UTF-8.
File content is from previous run, not current+
Immediate action
Check if you used append mode. Look at FileWriter constructor — missing second argument means overwrite.
Commands
Inspect source code: search for `new FileWriter` and verify the boolean flag. If not present, add `true`.
Run a quick test: create a writer with `true` and write a marker line, then restart app and verify marker still exists.
Fix now
Change constructor to new FileWriter(path, true). Also consider using StandardOpenOption.APPEND if using Files.newBufferedWriter.
FileReader/FileWriter vs NIO Files Methods
Feature / AspectFileReader / FileWriterFiles.readString / Files.writeString (NIO)
Java version introducedJava 1.1Java 11+
Reading entire fileRequires loop + StringBuilderOne method call
Writing entire stringRequires open, write, closeOne method call
Charset controlOnly via InputStreamReader wrapperBuilt-in Charset parameter
Best forStreaming large files line by lineSmall files, quick reads/writes
Buffering needed?Yes — always wrap with Buffered*Built in automatically
Append modenew FileWriter(path, true)StandardOpenOption.APPEND flag
Performance on large filesExcellent when bufferedLoads whole file into memory
Exception typeChecked IOExceptionChecked IOException
Closing resourcesMust use try-with-resourcesHandled internally

Key takeaways

1
FileWriter has two modes
overwrite (default) and append (pass true as the second argument) — getting this wrong causes silent data loss with no exception or warning.
2
Always wrap FileReader in BufferedReader and FileWriter in BufferedWriter
raw FileReader/FileWriter make one OS system call per character, which kills performance on anything larger than a few lines.
3
Try-with-resources is non-negotiable
if you don't close a FileWriter, its internal buffer may never flush to disk, meaning your data never actually gets written even though no exception was thrown.
4
FileReader and FileWriter inherit the platform's default charset
for any file with non-ASCII content, switch to InputStreamReader/OutputStreamWriter with an explicit StandardCharsets.UTF_8 argument to avoid cross-platform encoding bugs.
5
For very small files (under ~10MB), consider using the Files.readString() and Files.writeString() methods from Java NIO (11+)
they are simpler, charset-safe, and automatically manage resources.

Common mistakes to avoid

4 patterns
×

Not wrapping FileReader/FileWriter with their Buffered counterparts

Symptom
Code works correctly but is extremely slow on files larger than a few KB because every character read/write is a separate OS system call.
Fix
Always write new BufferedReader(new FileReader(path)) and new BufferedWriter(new FileWriter(path)); never use FileReader or FileWriter raw in production code.
×

Forgetting to close the stream (or not using try-with-resources)

Symptom
Data written with FileWriter appears missing or truncated in the output file, because the internal buffer was never flushed to disk.
Fix
Always use try-with-resources; if you must close manually, put writer.close() in a finally block, never just at the end of the try block where an exception could skip it.
×

Relying on the platform default charset

Symptom
Files with accented characters, emojis, or non-ASCII content display as garbage (e.g., '??' or 'é' as 'é') on a different machine or OS.
Fix
Use new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8) and new OutputStreamWriter(new FileOutputStream(path), StandardCharsets.UTF_8) instead of raw FileReader/FileWriter whenever your content might contain non-ASCII text.
×

Mistaking append mode for overwrite (or vice versa)

Symptom
Log files or incremental outputs are erased every time the program starts, or new data appears unexpectedly on top of old data.
Fix
Explicitly pass true for append mode: new FileWriter(path, true). For overwrite, pass false or use the one-arg constructor but document the intent. Never assume the default is correct.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Why should you always wrap FileReader with BufferedReader rather than us...
Q02SENIOR
What is the difference between new FileWriter('log.txt') and new FileWri...
Q03SENIOR
FileReader and FileWriter use the platform's default charset. Why is thi...
Q04SENIOR
Under what circumstances would you choose Files.readString() over FileRe...
Q01 of 04SENIOR

Why should you always wrap FileReader with BufferedReader rather than using FileReader directly? What exactly happens at the OS level if you don't?

ANSWER
FileReader.read() makes a system call to the OS for every single character. A 50KB file results in ~50,000 syscalls. BufferedReader reads an 8KB chunk into memory in one call, then serves subsequent reads from that buffer without touching the OS. This can be 10-100x faster and also provides the essential readLine() method.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between FileReader and BufferedReader in Java?
02
Does Java FileWriter create the file if it doesn't exist?
03
When should I use FileReader/FileWriter versus Java NIO Files.readString or Files.writeString?
04
What happens if I forget to close a FileWriter?
05
Can I use FileReader and FileWriter for binary files?
🔥

That's Java I/O. Mark it forged?

5 min read · try the examples if you haven't

Previous
File Handling in Java
2 / 8 · Java I/O
Next
BufferedReader and BufferedWriter