Java Writing Files

In the last lesson you learned about Java reading files. Reading is only half of it. To save data so it lasts, you need to write files. Let’s learn how.

πŸ€” Why write files?

Think of a program’s memory like a whiteboard. While the program runs, your numbers and names sit there. The moment the program closes, the whiteboard is wiped clean. A file is like a notebook. What you write stays, even after the program ends and the computer restarts.

Real programs write files to:

  • Save a user’s progress or settings for next time the app opens.
  • Export results that someone can open in Excel, a text editor, or another program.
  • Append events to a log file so you can see later what happened and when. (A log file is a plain text file with one line per event, usually with a timestamp.)
  • Hand data to another program, since a file is a simple way to share.

Writing can fail, so the file-writing methods throw a checked exception called IOException:

  • The disk could be full, or the folder might not exist.
  • The file might be locked, or you may not have permission.
  • A checked exception is one the compiler forces you to handle, by catching it or passing it on.
  • You cannot ignore it and still compile.

✏️ The simplest writer: FileWriter

The most direct tool for writing text is FileWriter:

  • You give it a file name, then call write to put text in.
  • If the file does not exist, FileWriter creates it for you.
  • If it does exist, by default it erases the old content first (more on that danger soon).

Here is the smallest possible example that writes one line of text.

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, Alex!"); // βœ… writes text into the file
System.out.println("Done writing.");
} catch (IOException e) {
System.out.println("Could not write the file: " + e.getMessage());
}
}
}

What each part does:

  • new FileWriter("greeting.txt") opens the file, creating it if needed.
  • writer.write("Hello, Alex!") sends the text into the file.
  • The try (...) is try-with-resources. It closes the file when the block finishes.
  • The catch runs only if something goes wrong, like a disk problem.

Use FileWriter for a small amount of text and the least code. The downside is speed. Each write can go straight to the disk, and disk trips are slow. For lots of text in small pieces, BufferedWriter is faster.

Output

Done writing.

πŸš€ Writing efficiently with BufferedWriter

BufferedWriter is a wrapper you put around a FileWriter:

  • The word buffer means a small holding area in memory.
  • It collects your text in the buffer instead of sending each piece straight to the disk.
  • It sends the text to the disk in bigger chunks.
  • Fewer disk trips means faster writing.

Picture it: a FileWriter alone carries water one cup at a time. A BufferedWriter fills a bucket first, then pours the whole bucket.

This example writes two lines using a buffered writer.

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
writer.write("Hello, file!");
writer.newLine(); // βœ… moves to the next line
writer.write("Second line.");
System.out.println("Done writing.");
} catch (IOException e) {
System.out.println("Could not write the file: " + e.getMessage());
}
}
}

Reading the code top to bottom:

  • new FileWriter("output.txt") is the real connection to the file.
  • new BufferedWriter(...) wraps it to add the speed buffer on top.
  • writer.write(...) adds text, the same as before.
  • writer.newLine() adds the correct line break for your operating system.
  • Windows and Linux use different β€œend of line” characters. Typing "\n" yourself may look wrong on Windows, but newLine() always picks the right one.

Output

Done writing.

The console only prints Done writing., but output.txt now holds the two lines you wrote.

πŸ–¨οΈ PrintWriter for convenient formatting

A third writer worth knowing is PrintWriter:

  • It gives you the same println and printf you already use with System.out.
  • println adds the line break for you, so you skip newLine().
  • printf formats numbers and text with placeholders.

This example writes a small report using println and printf.

import java.io.PrintWriter;
import java.io.FileWriter;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try (PrintWriter writer = new PrintWriter(new FileWriter("report.txt"))) {
writer.println("Daily Report"); // βœ… println adds the line break
writer.println("------------");
writer.printf("Name: %s%n", "Riya"); // %s is text, %n is a new line
writer.printf("Score: %d%n", 95); // %d is a whole number
System.out.println("Report written.");
} catch (IOException e) {
System.out.println("Could not write: " + e.getMessage());
}
}
}

What the placeholders mean:

  • %s is replaced by text, here the name Riya.
  • %d is replaced by a whole number, here 95.
  • %n is a line break, the file-friendly cousin of newLine().

Pick PrintWriter for neat, formatted output and the println/printf style. Pick plain BufferedWriter when you push raw text and want full control over line breaks.

Output

Report written.

πŸ†• The modern way: Files.write and Files.writeString

Since Java 7, the Files helper class makes writing a one-liner:

  • It is part of NIO, the newer file tools in Java. NIO stands for New Input/Output.
  • You do not create a writer, wrap it, or close it yourself.
  • You just hand over a path and the content.

This example writes a list of lines in a single call.

import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> lines = Arrays.asList("First line", "Second line", "Third line");
try {
Files.write(Paths.get("output.txt"), lines); // βœ… writes all lines at once
System.out.println("File written.");
} catch (IOException e) {
System.out.println("Could not write: " + e.getMessage());
}
}
}

What that one important line does:

  • Paths.get("output.txt") turns the file name into a Path object.
  • Files.write(path, lines) writes every item in the list, each on its own line.
  • It opens, writes, and closes the file for you in that single call.

If you have just one String instead of a list, use Files.writeString (added in Java 11). It writes the whole String in one go.

import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try {
Files.writeString(Paths.get("note.txt"), "A short note saved in one line.");
System.out.println("Note saved.");
} catch (IOException e) {
System.out.println("Could not write: " + e.getMessage());
}
}
}

Reach for these Files methods when your content is ready, either as a list of lines or a single String, and you just want to drop it into a file. They throw IOException, so you still handle that.

Output

File written.

βž• Appending instead of overwriting

This is the part beginners most often get wrong, so read it slowly. There are two modes when you open a file for writing:

  • Overwrite mode wipes the file clean and starts fresh. This is the default.
  • Append mode keeps the existing content and adds your new text at the end.
  • The default overwrite is the trap. Write to an important file the normal way and the old content is gone, with no undo.
  • Imagine a log file holding a week of events. Open it the default way, write one line, and you deleted a week of history.

To switch on append mode with FileWriter, pass true as the second argument.

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
// the true here means "append", not "overwrite"
try (BufferedWriter writer = new BufferedWriter(new FileWriter("log.txt", true))) {
writer.write("New log entry"); // βœ… added to the end, old lines kept
writer.newLine();
System.out.println("Entry appended.");
} catch (IOException e) {
System.out.println("Could not append: " + e.getMessage());
}
}
}

The whole difference is that small true:

  • With it, each run adds a line to the end.
  • Without it, each run erases everything that was there.
  • For logs, where every event should be kept, append is what you want.

The Files class does the same with an option called StandardOpenOption.APPEND. You pass it after the content.

import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> moreLines = Arrays.asList("Another entry");
try {
Files.write(Paths.get("log.txt"), moreLines, StandardOpenOption.APPEND);
System.out.println("Appended with Files.");
} catch (IOException e) {
System.out.println("Could not append: " + e.getMessage());
}
}
}

A few notes on StandardOpenOption.APPEND:

  • The file must already exist, or you get an error.
  • To create it when missing and append when present, add StandardOpenOption.CREATE alongside APPEND.
  • Plain Files.write without options always creates or overwrites, so you only add these options to append.

Output

Entry appended.

πŸ’§ Flushing and why closing matters

Remember the buffer, the holding area in memory? It causes one surprise:

  • When you call write, your text often goes into the buffer first, not the disk.
  • The text sits there until the buffer fills up or you send it down on purpose.
  • If your program ends or crashes first, the buffered text is lost.
  • The file may end up empty or missing your last lines.

Sending the buffered text to the disk is called flushing. There are two ways it happens:

  • You call writer.flush() yourself to force everything out now.
  • You close the writer, and closing flushes automatically as its last act.

So closing is not just cleanup. It guarantees your data reaches the disk. The clean way to always close is try-with-resources, which you have seen in every example. Java closes the writer when the block ends, even on error. Compare the two styles below.

// ❌ Risky: you must remember to close, and an error may skip it
BufferedWriter writer = new BufferedWriter(new FileWriter("data.txt"));
writer.write("important data");
// if an exception happens here, close() never runs and data may be lost
writer.close();
// βœ… Safe: try-with-resources always closes (and flushes) for you
try (BufferedWriter w = new BufferedWriter(new FileWriter("data.txt"))) {
w.write("important data");
} // w is closed and flushed here, even if an error happened

So always open writers with try-with-resources. You get the flush and the close for free, and your data is safe.

πŸ§ͺ A worked example: overwrite, then append

Let’s tie it together with one realistic run. First we overwrite a fresh file with three lines. Then a separate program appends one line. Watching the file before and after makes the two modes clear.

This first program writes three lines, replacing any old content.

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("scores.txt"))) {
writer.write("Alex scored 80");
writer.newLine();
writer.write("Riya scored 95");
writer.newLine();
writer.write("Sam scored 73");
writer.newLine();
System.out.println("Three lines written (overwrite).");
} catch (IOException e) {
System.out.println("Write failed: " + e.getMessage());
}
}
}

After this runs, scores.txt holds exactly three lines.

Output

Alex scored 80
Riya scored 95
Sam scored 73

Now a second program appends one more line, keeping the three above. Notice the true for append mode.

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("scores.txt", true))) {
writer.write("Maya scored 88"); // βœ… added to the end, others kept
writer.newLine();
System.out.println("One line appended.");
} catch (IOException e) {
System.out.println("Append failed: " + e.getMessage());
}
}
}

After the append, the file has all four lines. The first three stayed because we used append mode.

Output

Alex scored 80
Riya scored 95
Sam scored 73
Maya scored 88

Leave out the true in the second program and the file would hold only Maya scored 88. That single argument is the line between adding to your data and destroying it.

⚠️ Common Mistakes

A few file-writing slip-ups to watch for.

  • Accidentally overwriting a file. Opening for writing erases the file by default, so you can wipe out data without meaning to.
new FileWriter("log.txt"); // ❌ erases the whole log
new FileWriter("log.txt", true); // βœ… keeps the log, adds to the end
  • Not closing the writer, so data is lost. Text can stay stuck in the buffer and never reach the disk.
BufferedWriter w = new BufferedWriter(new FileWriter("data.txt"));
w.write("hello"); // ❌ never closed, "hello" may be lost
try (BufferedWriter w = new BufferedWriter(new FileWriter("data.txt"))) {
w.write("hello"); // βœ… closed and flushed automatically
}
  • Ignoring IOException. Writing is a checked exception, so the program will not compile if you pretend it cannot fail.
Files.write(Paths.get("a.txt"), lines); // ❌ does not compile, IOException unhandled
try {
Files.write(Paths.get("a.txt"), lines); // βœ… handled
} catch (IOException e) {
System.out.println("Write failed: " + e.getMessage());
}

βœ… Best Practices

Habits for writing files.

  • Use try-with-resources every time. It closes the writer and flushes the buffer, so your data is safely saved even when an error happens.
  • Choose append vs overwrite on purpose. Use append (true or StandardOpenOption.APPEND) for logs and growing files. Use overwrite only when you truly mean to replace the content.
  • Wrap a FileWriter in a BufferedWriter for lots of text. The buffer makes many small writes much faster.
  • Reach for Files.write or Files.writeString for simple dumps. When you already have a list of lines or a single String, this is the shortest, clearest code.
  • Always handle IOException. Report failures with a clear message instead of letting the program crash silently.

🧩 What You’ve Learned

Nicely done. Let’s recap writing files.

  • βœ… Writing files makes data permanent on disk, surviving after the program ends.
  • βœ… FileWriter is the simplest writer; BufferedWriter wraps it for speed and gives you newLine().
  • βœ… PrintWriter adds friendly println and printf formatting.
  • βœ… Files.write writes a list of lines and Files.writeString writes a String, each in one call.
  • βœ… Writing overwrites by default; use append mode (true or StandardOpenOption.APPEND) to keep existing content.
  • βœ… Closing the writer flushes the buffer, so use try-with-resources and always handle IOException.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What happens to a file's existing content when you open it for writing by default?

    Why: By default, opening a file for writing erases its contents; use append mode to keep them.

  2. 2

    How do you append to a file with FileWriter instead of overwriting?

    Why: new FileWriter(name, true) opens the file in append mode, keeping existing content.

  3. 3

    What does Files.write do?

    Why: Files.write saves the given content to a file in a single call and handles closing.

  4. 4

    Why use try-with-resources when writing?

    Why: try-with-resources closes and flushes the writer so all data is written, even on error.

πŸš€ What’s Next?

You have used the classic streams and the Files helper. Next, let’s look at a reader built for efficient, line-by-line reading: BufferedReader. It pairs naturally with the writers you just learned.

Java BufferedReader

Share & Connect