Writing Files
Writing Files
Writing a file in Java involves a choice similar to reading one — a raw character-based writer, a one-line Files call, formatted output through PrintWriter, or raw bytes for binary data — and a choice that trips up newcomers more than any other: whether each write overwrites the file or appends to it.
What Is the Simplest Way to Write a File?
Writing text with a raw FileWriter works, but needs a try-with-resources block and one write() call per piece of content. Files.writeString() does the same job in one call, with the entire content passed as a single argument.
1// File: BeforeWritingFiles.java
2import java.io.*;
3import java.nio.file.*;
4
5public class BeforeWritingFiles {
6 public static void main(String[] args) throws IOException {
7 Path tempFile = Files.createTempFile("before-write", ".txt");
8
9 try (FileWriter writer = new FileWriter(tempFile.toFile())) {
10 writer.write("Invoice #2001\n");
11 writer.write("Total: 4500.0\n");
12 }
13
14 System.out.print(Files.readString(tempFile));
15
16 Files.delete(tempFile);
17 }
18}Output:
Invoice #2001
Total: 4500.0
1// File: AfterWritingFiles.java
2import java.io.IOException;
3import java.nio.file.*;
4
5public class AfterWritingFiles {
6 public static void main(String[] args) throws IOException {
7 Path tempFile = Files.createTempFile("after-write", ".txt");
8
9 Files.writeString(tempFile, "Invoice #2001\nTotal: 4500.0\n");
10
11 System.out.print(Files.readString(tempFile));
12
13 Files.delete(tempFile);
14 }
15}Output:
Invoice #2001
Total: 4500.0
Both produce an identical file — the difference that actually matters in practice is what happens on the second write to the same file, covered next.
How It Works Internally
By default, both FileWriter and Files.writeString() overwrite a file's existing content on every write. Appending instead of overwriting requires opting in explicitly — StandardOpenOption.APPEND for Files.writeString().
One sentence before the diagram: overwrite mode always resets the write position to the start of the file, while append mode always moves it to the end first.
Overwrite mode (default): Append mode (StandardOpenOption.APPEND):
file: [old content] file: [old content]
^ ^
write starts here, write starts here,
old content is truncated old content stays intact
1// File: AppendModeExample.java
2import java.io.IOException;
3import java.nio.file.*;
4
5public class AppendModeExample {
6 public static void main(String[] args) throws IOException {
7 Path tempFile = Files.createTempFile("append-demo", ".txt");
8
9 Files.writeString(tempFile, "first line\n");
10 Files.writeString(tempFile, "second line\n");
11 System.out.println("Without append: [" + display(tempFile) + "]");
12
13 Files.writeString(tempFile, "first line\n");
14 Files.writeString(tempFile, "second line\n", StandardOpenOption.APPEND);
15 System.out.println("With append: [" + display(tempFile) + "]");
16
17 Files.delete(tempFile);
18 }
19
20 private static String display(Path file) throws IOException {
21 return Files.readString(file).strip().replace("\n", "\\n");
22 }
23}Output:
Without append: [second line]
With append: [first line\nsecond line]
Every one of Java's file-writing APIs overwrites by default. Append mode is always something you opt into explicitly — never something you get by accident.
PrintWriter adds print(), println(), and printf() on top of an underlying writer, making it convenient for writing formatted text without building a String first.
1// File: PrintWriterExample.java
2import java.io.*;
3import java.nio.file.*;
4
5public class PrintWriterExample {
6 public static void main(String[] args) throws IOException {
7 Path tempFile = Files.createTempFile("printwriter-demo", ".txt");
8
9 try (PrintWriter writer = new PrintWriter(new FileWriter(tempFile.toFile()))) {
10 writer.printf("Item: %s, Qty: %d, Price: %.2f%n", "Notebook", 3, 45.5);
11 }
12
13 System.out.print(Files.readString(tempFile));
14
15 Files.delete(tempFile);
16 }
17}Output:
Item: Notebook, Qty: 3, Price: 45.50
Real-World Example
A customer support tool logs each message in a live chat to a transcript file as it happens, appending one line per message rather than rewriting the whole file every time — exactly the append-versus-overwrite distinction this article's internals section covers, applied to a case where getting it wrong is immediately obvious to a customer.
1// File: ChatTranscriptLogger.java
2import java.io.IOException;
3import java.nio.file.*;
4
5public class ChatTranscriptLogger {
6
7 private final Path transcriptFile;
8
9 public ChatTranscriptLogger(Path transcriptFile) throws IOException {
10 this.transcriptFile = transcriptFile;
11 if (!Files.exists(transcriptFile)) {
12 Files.createFile(transcriptFile);
13 }
14 }
15
16 public void append(String speaker, String message) throws IOException {
17 String line = speaker + ": " + message + "\n";
18 Files.writeString(transcriptFile, line, StandardOpenOption.APPEND);
19 }
20}1// File: ChatTranscriptLoggerDemo.java
2import java.io.IOException;
3import java.nio.file.*;
4
5public class ChatTranscriptLoggerDemo {
6 public static void main(String[] args) throws IOException {
7 Path transcriptFile = Files.createTempFile("transcript", ".txt");
8
9 ChatTranscriptLogger logger = new ChatTranscriptLogger(transcriptFile);
10 logger.append("Customer", "My order hasn't arrived yet");
11 logger.append("Agent", "Let me check that for you");
12 logger.append("Customer", "Thank you");
13
14 String fullTranscript = Files.readString(transcriptFile);
15 System.out.print(fullTranscript);
16
17 Files.delete(transcriptFile);
18 }
19}Output:
Customer: My order hasn't arrived yet
Agent: Let me check that for you
Customer: Thank you
A mistake that appears often in fresher pull requests is writing each new log entry with the default, overwriting open mode, only to discover in production that the log file only ever contains the most recent entry. Opening every append with StandardOpenOption.APPEND explicitly, exactly as ChatTranscriptLogger does here, is what keeps every prior message intact as new ones arrive. Every meaningful Files write method here also declares throws IOException — a disk that fills up or a permissions error needs a real plan for handling failure, not just a hope that the write succeeds.
Best Practices
Use Files.writeString() or Files.write() for straightforward single-call writes, reserving FileWriter or PrintWriter for cases needing incremental writes or formatted output.
Decide explicitly whether each write should overwrite or append, and reach for StandardOpenOption.APPEND deliberately rather than relying on the default overwrite behavior by accident.
Ensure a file's parent directory exists with Files.createDirectories() before writing to a path that might not have one yet, since Files.writeString() does not create missing parent directories on its own.
Use PrintWriter.printf() when the output has a clear, repeated structure — formatting the string directly at the write call avoids an extra intermediate String for simple cases.
Common Mistakes
Writing to a path whose parent directory does not exist throws, since none of Files's write methods create missing parent directories automatically.
1// File: MissingParentDirectoryMistake.java
2import java.io.IOException;
3import java.nio.file.*;
4
5public class MissingParentDirectoryMistake {
6 public static void main(String[] args) {
7 Path missingParent = Path.of("no-such-directory-42", "report.txt");
8 try {
9 Files.writeString(missingParent, "data");
10 } catch (IOException e) {
11 System.out.println("Caught: " + e.getClass().getSimpleName());
12 }
13 }
14}Output:
Caught: NoSuchFileException
Assuming new FileWriter(file) appends by default is a second, very common mistake — the single-argument constructor overwrites, silently discarding whatever was written before.
1// File: FileWriterOverwriteMistake.java
2import java.io.*;
3import java.nio.file.*;
4
5public class FileWriterOverwriteMistake {
6 public static void main(String[] args) throws IOException {
7 Path tempFile = Files.createTempFile("filewriter-mistake", ".txt");
8
9 try (FileWriter writer = new FileWriter(tempFile.toFile())) {
10 writer.write("first save\n");
11 }
12 try (FileWriter writer = new FileWriter(tempFile.toFile())) {
13 writer.write("second save\n");
14 }
15
16 System.out.print(Files.readString(tempFile));
17
18 Files.delete(tempFile);
19 }
20}Output:
second save
"first save" is completely gone — the second FileWriter opened the file in its default, overwriting mode. new FileWriter(file, true) is the two-argument constructor that appends instead.
Interview Questions
Q1. What are the main ways to write to a file in Java?
Files.writeString() or Files.write() for a single-call write of text or bytes, FileWriter for incremental character writes, PrintWriter for formatted output built on top of a writer, and Files.write() again specifically for raw binary data. The nuance interviewers listen for is whether you can explain when each is the better choice, not just list them.
Q2. Does Files.writeString() overwrite an existing file by default?
Yes. Without an explicit StandardOpenOption.APPEND, every call to Files.writeString() on an existing file replaces its entire content. This is one of the most common assumptions freshers get backwards, which is exactly why it comes up so often.
Q3. How do you append to an existing file instead of overwriting it?
Pass StandardOpenOption.APPEND as an additional argument to Files.writeString() or Files.write(), or use the two-argument FileWriter(File, boolean) constructor with true for the older character-stream API. A strong answer names both the modern and legacy mechanism, not just one.
Q4. What happens if you call Files.writeString() with a path whose parent directory does not exist?
It throws NoSuchFileException, exactly as demonstrated in this article's Common Mistakes section — Files.createDirectories() needs to be called first to ensure the parent directory exists. Interviewers listen for whether you connect this back to defensive coding around user-supplied or dynamically-built paths.
Q5. What is the difference between FileWriter and PrintWriter?
FileWriter writes raw character data with a basic write() method. PrintWriter wraps another writer and adds convenience methods like print(), println(), and printf() for formatted output, without throwing a checked exception on write failures the way FileWriter does. The nuance being tested is awareness that PrintWriter swallows exceptions by design, which product-company interviewers often probe further.
Q6. Does new FileWriter(file) append or overwrite by default?
It overwrites. The single-argument constructor always opens the file in truncating mode — the two-argument constructor, new FileWriter(file, true), is required to append instead. This is essentially the same question as Q2 phrased around the legacy API, and interviewers ask it precisely because so many candidates only know one side.
Q7. What method should be used to write raw binary data to a file?
Files.write(Path, byte[]), which writes the exact bytes given with no character encoding involved, is the correct choice for binary data — the same role FileInputStream plays on the reading side. The nuance interviewers listen for is recognizing that encoding is irrelevant for binary data, not an afterthought to worry about.
FAQs
Does Files.writeString() create the file if it doesn't already exist?
Yes, by default it creates the file if it does not exist, in addition to truncating it if it does — this is the standard default open-option combination used when no explicit OpenOption arguments are given.
Can Files.createDirectories() be used to ensure a parent directory exists before writing?
Yes, and it should be — Files.createDirectories(path) creates a directory along with any missing parent directories, and does nothing (without throwing) if the directory already exists.
What encoding does Files.writeString() use by default?
UTF-8, consistent with Files.readString() — both assume UTF-8 unless a different Charset is passed explicitly as an additional argument.
Is it safe to write to a file from multiple threads at the same time?
No, not without external coordination. Concurrent, uncoordinated writes to the same file from multiple threads can interleave unpredictably or corrupt the file's content — a lock or a single writer thread is needed to keep writes safe under concurrency.
Does PrintWriter throw a checked IOException on write failures?
No. PrintWriter's print(), println(), and printf() methods suppress IOException internally and instead set an internal error flag, checkable with checkError() — this is a deliberate design choice that trades explicit exception handling for more convenient, chainable output calls.
What is the difference between Files.write() and Files.writeString()?
Files.write() takes a byte[] and writes it exactly as given, with no encoding involved — appropriate for binary data. Files.writeString() takes a CharSequence, encodes it as UTF-8 by default, and writes the resulting bytes — appropriate for text.
Can StandardOpenOption be combined with other options, like CREATE, in one call?
Yes, Files.writeString() and Files.write() both accept any number of OpenOption values, so StandardOpenOption.CREATE and StandardOpenOption.APPEND can be passed together in the same call when both behaviors are needed explicitly.
Summary
Writing a file in Java ranges from a single Files.writeString() call for simple cases to PrintWriter for formatted output and Files.write() for raw bytes — but the single most consequential choice across all of them is whether a write overwrites or appends, since the default for every one of these APIs is to overwrite.
The habit worth carrying forward from this article's chat transcript example is deciding that choice deliberately every time, and remembering that neither Files.writeString() nor FileWriter's default constructor creates missing parent directories or preserves prior content without being told to.
What to Read Next
Learn how to read and write files faster with buffering.