Java FileWriter overwrites logs on restart - append mode
FileWriter's default mode overwrites files silently on JVM restart, causing log loss.
20+ years shipping production Java in banking & fintech. Written from production experience, not tutorials.
- ✓Solid grasp of fundamentals
- ✓Comfortable reading code examples
- ✓Basic production concepts
- 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".
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 call. BufferedWriter batches those writes into chunks. For anything longer than a few lines, always wrap.write()
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.
BufferedWriter.newLine() writes the correct line separator for whatever OS is running. Use it every time.write() call is a separate disk write.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.
new FileWriter(path, true).new FileWriter(path, false) or default.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.
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.while ((line = reader.readLine()) != null) is correct, but many novices write while (!line.isEmpty()) and miss the last line.Files.readString() (Java 11+).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.
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.Files.copy() (NIO) — simpler, faster, built-in buffering.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.
The Silent Resource Leak — Why FileReader/FileWriter Never Close Themselves
Every Java dev has written this: open a FileWriter, do some writes, forget to close. The file handle lingers. On Windows, the file stays locked. On Linux, you leak file descriptors until your app crashes with "Too many open files." The try-with-resources construct from Java 7 isn't optional—it's mandatory. Without it, you're gambling that your finally block always runs. In production, it won't. An exception in the write method skips your close call entirely. Buffered output remains in memory. Data vanishes. The JVM's finalizer might eventually close the stream, but that's non-deterministic and deprecated. Always declare your FileReader and FileWriter in the resource specification of a try-with-resources block. It calls close() automatically, even on exceptions. Your operating system will thank you.
The Buffer Tax — Reading One Character at a Time Will Crumble Under Load
FileReader.read() returns a single character. FileWriter.write(int) writes a single character. Chaining these in a loop is the slowest possible I/O pattern. Each call hits the file system or disk. On a 10MB text file, that's 10 million system calls. A task that should take 150ms will take 15 seconds in production. The fix is absurdly simple: wrap them in BufferedReader and BufferedWriter. These add an internal 8KB buffer (default) so you read and write in chunks. For line-oriented data, use readLine() and write(String) directly. The performance improvement is often 100x or more. Never benchmark a single-file copy with raw readers and writers. Always buffer. If you need maximum throughput for binary data, skip character streams entirely and go with FileInputStream/FileOutputStream paired with BufferedInputStream.
Production Incident: Log File Goes Missing After Server Restart
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.new FileWriter("transactions.log", true) to enable append mode. Also added a health check that validates the file still contains expected entries after restart.- 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.
close() is in a finally block. Check disk space and permissions.new FileWriter(path) overwrites. Use new FileWriter(path, true) for append. Check if a file deletion occurs elsewhere.line != null, not !line.isEmpty().ls -la /path/to/file (Linux) or dir C:\path\to\file (Windows) — check file size and permissionscat /path/to/file (Linux) or type C:\path\to\file (Windows) — view file contents immediatelytry(). For manual close, add finally block.| File | Command / Code | Purpose |
|---|---|---|
| WriteUserReport.java | public class WriteUserReport { | What FileWriter Actually Does |
| AppendEventLog.java | public class AppendEventLog { | Appending to a File |
| ReadConfigFile.java | public class ReadConfigFile { | Reading Files With FileReader |
| TextFileCopier.java | public class TextFileCopier { | Copying a Text File |
| Utf8FileExample.java | public class Utf8FileExample { | Character Encoding Pitfalls |
| SafeFileCopy.java | public class SafeFileCopy { | The Silent Resource Leak |
| BufferedFileCopy.java | public class BufferedFileCopy { | The Buffer Tax |
Key takeaways
Files.readString() and Files.writeString() methods from Java NIO (11+)Interview Questions on This Topic
Why should you always wrap FileReader with BufferedReader rather than using FileReader directly? What exactly happens at the OS level if you don't?
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.Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Written from production experience, not tutorials.
That's Java I/O. Mark it forged?
5 min read · try the examples if you haven't