Learn Java

Lesson 47Phase 5: Errors, files and the standard library

Writing files

Creating and writing to a file, append versus overwrite, and flushing and closing properly.

By Updated 50 min

  • FileWriter
  • BufferedWriter
  • append mode
  • flush
  • close
  • persisting data

In reading files you opened a file and read what was already there. Reading is only half the story. The other half makes a program feel real: writing your own text to a file, so that it is still there after the program ends, after the window closes, even after the computer is switched off.

By the end of this lesson
  • Write text to a file on disk so the data outlives the program that made it.
  • Wrap a FileWriter in a BufferedWriter and use write and newLine.
  • Choose between overwriting a file and appending to it, and predict the file contents either way.
  • Explain what flush and close do, and why forgetting them quietly loses your data.
  • Write lines with a PrintWriter, and read back a file your program saved.
From before
In reading files you read with FileReader and BufferedReader, closed files with try-with-resources, and put throws IOException on main because every file operation can fail. Writing is the mirror image of all of that. You will also use loops and arrays to write several lines at once.
Watching the files
The programs on this page run on the same in-browser disk as the ones in reading files. Most of them print nothing, because their result is a file. Run one, then look at the memory view under the code: its Files section shows every file and its contents, changing step by step. The narration says each time text goes into a buffer and each time it reaches the file.

Why a file at all? Because variables forget#

Everything you have printed so far went to the console with System.out.println. That text scrolls past and is gone. Every variable you have made lives in memory, and memory is wiped clean the moment the program ends. Run the program again tomorrow and it remembers nothing about today.

A file lives on the disk instead. When you write text into a file, that text stays on the disk after main returns, after the JVM shuts down, after you close your laptop. The next program to run can open the same file and read it back. Making data survive past the life of the program that produced it is called persisting it.

Key idea

Console output is for a person to glance at and forget. A file is for data you want to keep. Writing to a file is how a program leaves something behind.

Coming from Python?
Python's open("log.txt", "w") and f.write(...)have the same shape as Java's writers: open a file for writing, push text into it, then close it. Java's classes have longer names, and each one does one part of the job.

The smallest write: FileWriter#

The plainest tool for writing text is FileWriter. You give it a file name, you call write with some text, and you close it. Here is the whole thing:

WriteOnce.java
1import java.io.FileWriter;
2import java.io.IOException;
3
4public class Main {
5 public static void main(String[] args) throws IOException {
6 FileWriter writer = new FileWriter("greeting.txt");
7 writer.write("Hello, file.");
8 writer.close();
9 }
10}

Run it and nothing appears in the console. Look in the memory view instead: the Files section now holds greeting.txt, with Hello, file. inside. Read the program line by line. new FileWriter("greeting.txt") opens a file by that name for writing, and creates it if it does not exist yet. writer.write("Hello, file.") hands the text over to be written. writer.close() finishes the job and lets go of the file.

The imports bring in the two classes, as they did for reading, and throws IOException on main is there for the same reason as before: writing can fail. The disk might be full, the folder might be read-only, or the name might point into a folder that does not exist.

Wrapping it: BufferedWriter#

When you read files, you wrapped a FileReader inside a BufferedReader so you could read a whole line at a time and so it ran faster. Writing has the mirror image: you wrap a FileWriter inside a BufferedWriter.

A buffer is a holding area in memory. Instead of sending each small piece of text to the disk the instant you write it, the BufferedWriter gathers your text in memory and sends it to the disk in one larger batch. Touching the disk is slow, so doing it once with a big chunk is far cheaper than doing it a hundred times with small ones.

WriteBuffered.java
1import java.io.BufferedWriter;
2import java.io.FileWriter;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 BufferedWriter out = new BufferedWriter(new FileWriter("names.txt"));
8 out.write("Aisha");
9 out.newLine();
10 out.write("Ben");
11 out.newLine();
12 out.write("Carmen");
13 out.newLine();
14 out.close();
15 }
16}

Step through it and watch the Files section. names.txt appears, empty, as soon as the file is opened. Each write and newLine only puts text into the buffer, and the file stays empty. Then close() runs, and all three names reach the file at once.

Two more things to notice. First, the wrapping: new BufferedWriter(new FileWriter("names.txt")) reads from the inside out. The FileWriter is built first and knows about the file; the BufferedWriter goes around it and adds the buffering. You then talk only to the outer BufferedWriter.

Second, newLine. The write method does not move to a new line by itself. Three write calls in a row with no newLine would give AishaBenCarmen, all jammed together on one line. out.newLine() adds a line break, so each name lands on its own line. It is the writing counterpart of pressing Enter.

Quick check
A program runs out.write("a"); out.write("b"); out.newLine(); out.write("c"); and then closes out. What does the file hold?
newLine or a \n inside the string?
You could also write out.write("Aisha\n"), with a line break inside the string. On macOS and Linux the file comes out the same. newLine() writes the line ending of the system the program runs on, which on Windows is the two characters \r\n, so files written there look right to other Windows programs.

Overwrite or append: the one-argument question#

Here is the most common surprise in file writing. When you open a file with new FileWriter("report.txt"), Java throws away whatever was already in that file and starts from empty. This is called overwriting, or truncating. The old contents are gone before you write a single character.

Often that is what you want. Sometimes, though, you want to add to a file and keep what was there: a running log, or a list you build up over many runs. For that you pass a second argument, true, which switches the file writer into append mode. In append mode, new text is added at the end and the old contents stay put.

Overwrite (default)
Append (pass true)
You write new FileWriter("log.txt").
You write new FileWriter("log.txt", true).
Existing contents are erased first.
Existing contents are kept.
New text starts from an empty file.
New text is added at the end.
Good for a fresh report each run.
Good for a log that grows over time.
Run it twice: the file has only the second run.
Run it twice: the file has both runs.

Overwrite mode#

Before this program runs, report.txtholds Sunday's report, shown under the code. The program opens the file without a second argument and writes Monday's:

Overwrite.java
1import java.io.BufferedWriter;
2import java.io.FileWriter;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 BufferedWriter out = new BufferedWriter(new FileWriter("report.txt"));
8 out.write("Report for Monday");
9 out.newLine();
10 out.write("Sales: 12");
11 out.newLine();
12 out.close();
13 }
14}
Files before the program runs
report.txt
Report for Sunday
Sales: 9

Step through it and watch report.txt. On line 7, the moment the file is opened, Sunday's report vanishes and the file is empty. After close()it holds only Monday's two lines. Run the same program tomorrow and you get the same two lines again, never four.

Append mode#

Now the same idea with true. Each call to addEntry below opens log.txt in append mode, adds one line and closes the file, the same as one run of a program that logs a single line. The log starts with one line from Sunday:

Append.java
1import java.io.BufferedWriter;
2import java.io.FileWriter;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 addEntry("Monday: opened");
8 addEntry("Monday: closed");
9 }
10
11 static void addEntry(String text) throws IOException {
12 try (BufferedWriter out = new BufferedWriter(new FileWriter("log.txt", true))) {
13 out.write(text);
14 out.newLine();
15 }
16 }
17}
Files before the program runs
log.txt
Sunday: opened

Because of the true, Sunday's line stays, and each call adds one more line at the bottom. The log ends with three lines and keeps growing every time something is added. That one extra argument, true, is the whole difference between a fresh report and a growing log.

The most common file-writing bug
Reaching for new FileWriter(path) when you meant to add to a file silently destroys everything that was in it. There is no warning and no undo. If your job is to add a line to a log and you forget the true, you have just deleted the whole log. Whenever you open a file for writing, pause and ask: do I want to replace this file, or grow it?
Predict the outputSaves.java
1import java.io.BufferedReader;
2import java.io.FileReader;
3import java.io.FileWriter;
4import java.io.IOException;
5
6public class Main {
7 public static void main(String[] args) throws IOException {
8 save("one", false);
9 save("two", true);
10 save("three", false);
11 save("four", true);
12
13 try (BufferedReader in = new BufferedReader(new FileReader("log.txt"))) {
14 String line = in.readLine();
15 while (line != null) {
16 System.out.println(line);
17 line = in.readLine();
18 }
19 }
20 }
21
22 static void save(String text, boolean append) throws IOException {
23 try (FileWriter out = new FileWriter("log.txt", append)) {
24 out.write(text + "\n");
25 }
26 }
27}

The second argument of FileWriter is an ordinary boolean, so it can come from a variable. This program saves four times, then prints the file. What does it print?

It prints three and four. two was appended after one, but the third save opens the file without appending, which wipes both. four is then appended after three.

Quick check
report.txt holds three lines. A program opens it with new FileWriter("report.txt"), writes one line with a line break, and closes it. How many lines does the file hold now?

flush and close: getting the buffer onto the disk#

A BufferedWriter holds your text in memory and only sends it to the disk in batches. That speed trick has a sharp edge. If the program ends while text is still sitting in the buffer, that text never reaches the file. It is simply lost, and no error tells you so. This program writes two names and forgets to close the writer:

NoClose.java
1import java.io.BufferedWriter;
2import java.io.FileWriter;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 BufferedWriter out = new BufferedWriter(new FileWriter("names.txt"));
8 out.write("Aisha");
9 out.newLine();
10 out.write("Ben");
11 out.newLine();
12 System.out.println("Wrote two names.");
13 }
14}
Prints
›Wrote two names.

The console says Wrote two names., and every write ran without an error. Now look at the Files section: names.txt exists, because opening it created it, but it is empty. The narration on the last step explains why: the program ended while out still held the names in its buffer. Open the program full screen, add out.close(); as the last line of main, and run it again to see the names arrive.

Two methods control when the buffer empties. flush pushes whatever is in the buffer out to the file right now and keeps the file open, so you can keep writing. close flushes for you and then releases the file, after which you can no longer write to it. Writing after close() throws an IOException with the message Stream closed.

Key idea

close is the call that guarantees your buffered text lands in the file. Skip it and the text can vanish without a single error message.

A plain FileWriter needs closing too
It is tempting to think the danger comes only from BufferedWriter. A FileWriter on its own keeps a buffer inside too, so WriteOnce.java without its writer.close() leaves greeting.txt empty as well. Every writer needs closing.

When would you flush instead of close?#

Use flush when you want the text in the file now, but you are not finished writing. A program that writes a log line every few seconds for hours wants each line safely on disk soon after it is written, in case the program crashes later. It does not want to close the file, because it has more to write. So it flushes after each line and closes only at the very end.

Predict the outputFlushCheck.java
1import java.io.BufferedReader;
2import java.io.BufferedWriter;
3import java.io.FileReader;
4import java.io.FileWriter;
5import java.io.IOException;
6
7public class Main {
8 public static void main(String[] args) throws IOException {
9 BufferedWriter out = new BufferedWriter(new FileWriter("log.txt"));
10 out.write("Started");
11 out.newLine();
12 out.flush();
13 out.write("Working");
14 out.newLine();
15
16 BufferedReader in = new BufferedReader(new FileReader("log.txt"));
17 String line = in.readLine();
18 while (line != null) {
19 System.out.println("log.txt: " + line);
20 line = in.readLine();
21 }
22 in.close();
23 }
24}

The writer is flushed after the first line but never closed. Before the program ends, a reader opens log.txt and prints what is in it. What does it print?

Only log.txt: Started appears. The flush() on line 12 pushed Started into the file. Working went into the buffer afterwards and was still waiting there when the reader looked, and because out is never closed, it never reaches the file at all.

flush()
close()
Pushes the buffer to the file now.
Pushes the buffer to the file, then lets the file go.
The file stays open. You can keep writing.
The file is closed. Writing again throws an IOException.
Use it partway through, to be safe.
Use it once, when you are done.
Does not release the file.
Releases the file for other programs and the system.
Quick check
Which call puts the buffered text into the file but lets you keep writing?

Letting try-with-resources close for you#

It is easy to forget close, especially if an error jumps you out of the method before you reach it. You met the fix when reading files: try-with-resources. Declare the writer inside parentheses right after try, and Java closes it for you when the block ends, whether it ends normally or because of an error. Because close flushes, this also guarantees that your buffer reaches the file.

WriteSafe.java
1import java.io.BufferedWriter;
2import java.io.FileWriter;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 int items = 42;
8 double total = 1260.0;
9
10 try (BufferedWriter out = new BufferedWriter(new FileWriter("report.txt"))) {
11 out.write("Daily report");
12 out.newLine();
13 out.write("Items sold: " + items);
14 out.newLine();
15 out.write("Total: " + total);
16 out.newLine();
17 }
18 }
19}

There is no out.close() anywhere in that code, and yet the file is flushed and closed. Step to the end: the narration says out is closed automatically, and the three lines appear in report.txt at that moment. This is the form to reach for by default.

The lines are ordinary strings built with +, the same string joining you have used all along. A file does not care that items is an int and total is a double. Once they are joined into a String, write puts those characters in the file. The double 1260.0 comes out with its trailing .0, as it would on the console.

write(42) does not write 42
write has a version that takes an int, and it treats the number as a character code. out.write(42) writes the single character whose code is 42, an asterisk. Join a number into a string first, as WriteSafe.java does, or use the PrintWriter coming up next, whose println(42) writes the digits.
WriteNumber.java
1import java.io.BufferedWriter;
2import java.io.FileWriter;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 int count = 42;
8 try (BufferedWriter out = new BufferedWriter(new FileWriter("count.txt"))) {
9 out.write(count);
10 out.newLine();
11 out.write("" + count);
12 out.newLine();
13 }
14 }
15}

After the run, count.txt holds * on the first line and 42 on the second.

Put the lines in orderShopping.java
Put the lines in order so that the program writes bread and eggs to shopping.txt, one per line, then reads the file back and prints bread and eggs. The writer's block must end, closing the file, before the reader opens it.

Tap a line, then tap where it should go. Or use the arrows.

Catching the failure
Every program on this page passes file problems on with throws IOException, so a failure stops the program with a report. The next lesson, exception handling, shows how to catch an IOException and respond to it instead, for example by printing a calm message and carrying on.

PrintWriter: println for files#

Writing every line as a write followed by a newLine gets repetitive. A PrintWriter wrapped around a FileWriter gives a file the same print and println methods that System.out has, so anything you know how to print to the console, you can print to a file:

SaveScores.java
1import java.io.FileWriter;
2import java.io.IOException;
3import java.io.PrintWriter;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 String[] names = {"Ada", "Grace", "Alan"};
8 int[] scores = {91, 88, 79};
9
10 try (PrintWriter out = new PrintWriter(new FileWriter("scores.txt"))) {
11 for (int i = 0; i < names.length; i++) {
12 out.println(names[i] + " " + scores[i]);
13 }
14 }
15 }
16}

This writes Ada 91, Grace 88 and Alan 79, one per line: the same scores.txt that the reading files lesson read back and averaged. printlnends each line with the system's line ending, like newLine(), and a PrintWriter needs closing like any other writer, which the try-with-resources block does.

Save, then load#

Persisting data means a program can find tomorrow what it saved today. The program below saves a to-do list, then opens the same file with the reader from the reading files lesson and prints what it finds. Trace how each write and newLine becomes one line of the file, and then one line of output.

SaveTodos.java
1import java.io.BufferedReader;
2import java.io.BufferedWriter;
3import java.io.FileReader;
4import java.io.FileWriter;
5import java.io.IOException;
6
7public class Main {
8 public static void main(String[] args) throws IOException {
9 String[] tasks = {"Buy milk", "Call Sam", "Finish notes"};
10
11 try (BufferedWriter out = new BufferedWriter(new FileWriter("todos.txt"))) {
12 out.write("To do");
13 out.newLine();
14 for (int i = 0; i < tasks.length; i++) {
15 out.write("- " + tasks[i]);
16 out.newLine();
17 }
18 }
19
20 try (BufferedReader in = new BufferedReader(new FileReader("todos.txt"))) {
21 String line = in.readLine();
22 while (line != null) {
23 System.out.println(line);
24 line = in.readLine();
25 }
26 }
27 }
28}
Prints
›To do
›- Buy milk
›- Call Sam
›- Finish notes

The first write puts the heading To do on line one. The loop runs three times, once per task, and each time it writes a dash, a space, the task, and a line break. When the first try block ends, the writer is closed and the four lines are in todos.txt. The second block reads them back, so the console shows the heading and the three dashed lines.

  1. 1Open the file for writing, choosing overwrite (the default) or append (true).
  2. 2Wrap it in a BufferedWriter or a PrintWriter.
  3. 3Call write and newLine, or println, for each line.
  4. 4Let close run, through try-with-resources, so the buffer reaches the file.
ExerciseTable.java
Write the three times table from 3 x 1 = 3 up to 3 x 5 = 15 to table.txt, one line each, using a loop inside the writer's try-with-resources block. The second half of the program reads the file back and prints each line with > in front, so you can check it.
One call instead of a writer
The newer file classes can write a whole file in one call, with no writer to close: Files.writeString(Path.of("greeting.txt"), "Hello, file."), and adding StandardOpenOption.APPEND makes it append. They come in Path and Files, after exception handling.

Try it

Answer each one in your head or on paper first, then open the answer.

1
A file scores.txt already contains the line 10. You run a program that opens it with new FileWriter("scores.txt") and writes 20 followed by a newLine. What does the file contain afterwards?
2
You change that program to open the file with new FileWriter("scores.txt", true) instead, starting again from a file that contains 10. Now what does the file contain after it writes 20 and a newLine?
3
A friend writes three lines to a BufferedWriter but never calls close or flush, and does not use try-with-resources. The program runs with no errors, but the file turns out empty. What happened?
4
In one sentence each, say what flush does and what close does, and which one you should always do when you are finished writing.
5
What does out.write(65) put in the file, and how would you write the digits 65 instead?
6
Why is try (BufferedWriter out = new BufferedWriter(new FileWriter("a.txt"))) safer than building the writer with a plain assignment and calling out.close() at the bottom yourself?

Practice

Work through these on your own. Each one builds on this page.

1
Write a program that saves the numbers 1 to 5 to a file called numbers.txt, one number per line, using a loop and a BufferedWriter in try-with-resources. Write down first exactly what the file should contain, then read it back in the same program to check.
2
You are building a log that should grow every time the program runs. Write the line that opens the file correctly, then explain in one or two sentences what would go wrong if you forgot the second argument, and what the log would look like after running the broken version three times.
3
Change the to-do program from this lesson so it appends to todos.txt instead of overwriting. Then work out exactly what the file contains after the program saves twice in a row: call the saving part twice, or put it in a method and call that twice, and read the file back to check.
4
Write a program that asks for three tasks at the keyboard with a Scanner, as in Scanner and console applications, and saves each one as a line of tasks.txt in append mode. Run it twice with different input and check that all six tasks are in the file.
5
In your own words, write a short paragraph explaining why a BufferedWriter is faster than writing each piece of text straight to the disk, and why that same speed trick is the reason forgetting to close can lose your data.

Further reading#

Short guides on this topic

Quick answers to what people search for about it, with code to run.

Spotted a mistake, or a part that did not make sense? Report it by email