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 BasithUpdated 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.
- Write text to a file on disk so the data outlives the program that made it.
- Wrap a
FileWriterin aBufferedWriterand usewriteandnewLine. - Choose between overwriting a file and appending to it, and predict the file contents either way.
- Explain what
flushandclosedo, and why forgetting them quietly loses your data. - Write lines with a
PrintWriter, and read back a file your program saved.
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.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 ideaConsole 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.
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:
import java.io.FileWriter;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { FileWriter writer = new FileWriter("greeting.txt"); writer.write("Hello, file."); writer.close(); }}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.
import java.io.BufferedWriter;import java.io.FileWriter;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { BufferedWriter out = new BufferedWriter(new FileWriter("names.txt")); out.write("Aisha"); out.newLine(); out.write("Ben"); out.newLine(); out.write("Carmen"); out.newLine(); out.close(); }}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.
out.write("a"); out.write("b"); out.newLine(); out.write("c"); and then closes out. What does the file hold?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.
new FileWriter("log.txt").new FileWriter("log.txt", true).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:
import java.io.BufferedWriter;import java.io.FileWriter;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { BufferedWriter out = new BufferedWriter(new FileWriter("report.txt")); out.write("Report for Monday"); out.newLine(); out.write("Sales: 12"); out.newLine(); out.close(); }}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:
import java.io.BufferedWriter;import java.io.FileWriter;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { addEntry("Monday: opened"); addEntry("Monday: closed"); } static void addEntry(String text) throws IOException { try (BufferedWriter out = new BufferedWriter(new FileWriter("log.txt", true))) { out.write(text); out.newLine(); } }}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.
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?import java.io.BufferedReader;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { save("one", false); save("two", true); save("three", false); save("four", true); try (BufferedReader in = new BufferedReader(new FileReader("log.txt"))) { String line = in.readLine(); while (line != null) { System.out.println(line); line = in.readLine(); } } } static void save(String text, boolean append) throws IOException { try (FileWriter out = new FileWriter("log.txt", append)) { out.write(text + "\n"); } }}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.
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:
import java.io.BufferedWriter;import java.io.FileWriter;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { BufferedWriter out = new BufferedWriter(new FileWriter("names.txt")); out.write("Aisha"); out.newLine(); out.write("Ben"); out.newLine(); System.out.println("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
closeis the call that guarantees your buffered text lands in the file. Skip it and the text can vanish without a single error message.
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.
import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { BufferedWriter out = new BufferedWriter(new FileWriter("log.txt")); out.write("Started"); out.newLine(); out.flush(); out.write("Working"); out.newLine(); BufferedReader in = new BufferedReader(new FileReader("log.txt")); String line = in.readLine(); while (line != null) { System.out.println("log.txt: " + line); line = in.readLine(); } in.close(); }}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.
IOException.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.
import java.io.BufferedWriter;import java.io.FileWriter;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { int items = 42; double total = 1260.0; try (BufferedWriter out = new BufferedWriter(new FileWriter("report.txt"))) { out.write("Daily report"); out.newLine(); out.write("Items sold: " + items); out.newLine(); out.write("Total: " + total); out.newLine(); } }}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 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.import java.io.BufferedWriter;import java.io.FileWriter;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { int count = 42; try (BufferedWriter out = new BufferedWriter(new FileWriter("count.txt"))) { out.write(count); out.newLine(); out.write("" + count); out.newLine(); } }}After the run, count.txt holds * on the first line and 42 on the second.
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.
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:
import java.io.FileWriter;import java.io.IOException;import java.io.PrintWriter; public class Main { public static void main(String[] args) throws IOException { String[] names = {"Ada", "Grace", "Alan"}; int[] scores = {91, 88, 79}; try (PrintWriter out = new PrintWriter(new FileWriter("scores.txt"))) { for (int i = 0; i < names.length; i++) { out.println(names[i] + " " + scores[i]); } } }}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.
import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { String[] tasks = {"Buy milk", "Call Sam", "Finish notes"}; try (BufferedWriter out = new BufferedWriter(new FileWriter("todos.txt"))) { out.write("To do"); out.newLine(); for (int i = 0; i < tasks.length; i++) { out.write("- " + tasks[i]); out.newLine(); } } try (BufferedReader in = new BufferedReader(new FileReader("todos.txt"))) { String line = in.readLine(); while (line != null) { System.out.println(line); line = in.readLine(); } } }}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.
- 1Open the file for writing, choosing overwrite (the default) or append (
true). - 2Wrap it in a
BufferedWriteror aPrintWriter. - 3Call
writeandnewLine, orprintln, for each line. - 4Let
closerun, through try-with-resources, so the buffer reaches the file.
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.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.
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?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?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?flush does and what close does, and which one you should always do when you are finished writing.out.write(65) put in the file, and how would you write the digits 65 instead?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 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.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.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.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#
- FileWriter in the Java SE 25 API documentation, including the constructor with the append argument
- BufferedWriter in the Java SE 25 API documentation, with newLine, flush and close
- PrintWriter in the Java SE 25 API documentation, including checkError
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