Writing files
Creating and writing to a file, append versus overwrite, and flushing and closing properly.
Finished reading?
Mark this session so you can track where you are.
Creating and writing to a file, append versus overwrite, and flushing and closing properly.
Finished reading?
Mark this session so you can track where you are.
Last session you opened a file and read what was already there. Reading is only half the story. The other half is the one that 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.
FileWriter in a BufferedWriter and use write and newLine.flush and close do, and why forgetting them can quietly lose your data.try-with-resources and why every file operation can throw an IOException. Writing is the mirror image of that, so keep it close. We will also lean on loops and arrays to write several lines at once.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 ever 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 is different. A file lives on the disk, not in memory. 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. This is what we mean by persisting data: making it survive past the life of the program that produced it.
Console output is for a human 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 then f.write(...). In C you had fopen with a "w" mode and fprintf. Java's building blocks have longer names, but the shape is identical: open a file for writing, push text into it, then close it. The longer names just spell out which piece does which job.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. We are showing it as a read-along block, not a Run button, because the in-browser playground cannot touch real files. The caption shows what the file on disk would contain after this runs.
import java.io.FileWriter;import java.io.IOException; public class Main { public static void main(String[] args) { try { FileWriter writer = new FileWriter("greeting.txt"); writer.write("Hello, file."); writer.close(); } catch (IOException e) { System.out.println("Could not write the file."); } }}After running, greeting.txt contains: Hello, file.
Read it line by line. new FileWriter("greeting.txt") opens a file by that name for writing and creates it if it does not yet exist. writer.write("Hello, file.") hands the text over to be written. writer.close() finishes the job and lets go of the file. The import lines at the top pull in the two classes we use, exactly as you did for reading.
try and catch (IOException e). You met this exact shape when reading files. We are not studying exceptions properly until next session, exception handling; for now, copy the shape and read it as “try to do this, and if a file error happens, run the catch instead”.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 exact mirror: you wrap a FileWriter inside a BufferedWriter.
The word buffer means a holding area. Instead of sending each tiny 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. The buffer is the difference.
import java.io.FileWriter;import java.io.BufferedWriter;import java.io.IOException; public class Main { public static void main(String[] args) { try { 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(); } catch (IOException e) { System.out.println("Could not write the file."); } }}After running, names.txt contains: Aisha Ben Carmen
Two new 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; if you called write three times in a row with no newLine, you would get 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("Aisha\n") with a literal newline character, and it would work. But newLine() is the cleaner habit, because it uses whatever line ending the current operating system expects. You do not have to think about it; it does the right thing on every machine.Here is the most common surprise with file writing, and the one most worth getting into your bones early. When you open a file with new FileWriter("log.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 fine. But sometimes you want to add to a file and keep what was there: a running log, 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).Suppose log.txt already contains the line old entry from a previous run. Now we run this overwrite program:
import java.io.FileWriter;import java.io.BufferedWriter;import java.io.IOException; public class Main { public static void main(String[] args) { try { BufferedWriter out = new BufferedWriter(new FileWriter("log.txt")); out.write("first run"); out.newLine(); out.write("second run"); out.newLine(); out.close(); } catch (IOException e) { System.out.println("Could not write the file."); } }}Before running, log.txt contained: old entry After running, log.txt contains: first run second run
The line old entry is gone. Because there is no true second argument, opening the file wiped it clean, and the file now holds only what this run wrote. If you run this program again, you get the same two lines, not four. Each run starts from empty.
Now the same idea with true. Start again from a file that contains old entry, and run this append program twice in a row:
import java.io.FileWriter;import java.io.BufferedWriter;import java.io.IOException; public class Main { public static void main(String[] args) { try { BufferedWriter out = new BufferedWriter(new FileWriter("log.txt", true)); out.write("new entry"); out.newLine(); out.close(); } catch (IOException e) { System.out.println("Could not write the file."); } }}Before running, log.txt contained: old entry After running it twice, log.txt contains: old entry new entry new entry
Because of the true, the original old entry stays, and each run adds one more new entry at the bottom. The first run leaves two lines, the second run leaves three. The file grows. That single extra word, true, is the entire 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?Remember that a BufferedWriter holds your text in a buffer 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 and was never pushed out, that text never reaches the file. It is simply lost.
Two methods control when the buffer empties. flush forces whatever is currently in the buffer out to the disk right now, while keeping the file open so you can keep writing. close does a flush for you and then releases the file, after which you can no longer write to it.
Calling
closeis not just good manners. It is the call that guarantees your buffered text actually lands in the file. Skip it and your data can vanish.
BufferedWriter and the program ends without close (or flush), those three lines may still be sitting in the buffer, never written. You will open the file later and find it empty, even though your write calls all ran. Every successful run of your program looked fine, yet the file is blank. Always close the writer.Use flush when you want the text on disk 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, but 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.
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. You declare the writer inside parentheses right after try, and Java promises to close it for you when the block ends, whether it ends normally or because of an error. Because close flushes, this also guarantees your buffer reaches the disk.
import java.io.FileWriter;import java.io.BufferedWriter;import java.io.IOException; public class Main { public static void main(String[] args) { 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(); } catch (IOException e) { System.out.println("Could not write the file."); } }}After running, report.txt contains: Daily report Items sold: 42 Total: 1260.0
There is no out.close() anywhere in that code, and yet the file is closed and flushed correctly. That is the whole gift of try-with-resources: by writing the writer inside the try (...) parentheses, you have told Java to close it for you the moment the block finishes. You cannot forget what you never had to type. This is the form to reach for by default.
Notice too that 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 you have joined them into a String, write simply puts those characters in the file. The double 1260.0 prints with its trailing .0, just as it would on the console.
IOException and printing a calm message, but we have not studied what an exception truly is, how catch works, or how to handle several kinds of failure. That is the entire next session, exception handling. For now, the try and catch shape is a template you copy; soon it will be something you fully understand and design yourself.Let us write a short program that saves a few lines of a to-do list to a file. Read it, then read the caption, and make sure you can see exactly how each write and newLine produces each line of the file.
import java.io.FileWriter;import java.io.BufferedWriter;import java.io.IOException; public class Main { public static void main(String[] args) { 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(); } } catch (IOException e) { System.out.println("Could not write the file."); } }}After running, todos.txt contains: To do - Buy milk - Call Sam - Finish notes
Trace it once in your head. 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 then a line break. So the file ends up with the heading and three dashed lines beneath it, exactly as the caption shows. Nothing is printed to the console; the result is the file.
true).BufferedWriter so you can write lines efficiently.write for text and newLine to end each line.close run, by hand or through try-with-resources, so the buffer reaches the disk.FileWriter opens the file; the path is just a name like todos.txt.BufferedWriter wraps it and gives you newLine and faster batched writing.true as the second argument means append.close flushes and releases; try-with-resources does it for you.Try each one yourself 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.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?Take these away. They continue exactly what we just did.
1 through 5 to a file called numbers.txt, one number per line, using a loop and a BufferedWriter. On paper, write out exactly what the file should contain. Use try-with-resources so you never call close by hand.todos.txt instead of overwriting. Then describe, on paper, what the file contains after you run that appending version twice in a row. Be exact about every line.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.