SwadhyAI Notes

All Java notes

Java BufferedWriter

›Short versionOpen if you only have a minute.
  • BufferedWriter collects characters and hands them to another Writer in chunks.
  • write() can return while those characters are still only in the buffer.
  • flush() pushes the buffer. close() flushes, then closes the wrapped writer.
  • A full disk or a broken stream stays quiet on a short write, and throws when the buffer is pushed.
  • If the process dies before that push, the characters are gone and nothing is thrown.
  • Name the charset on the writer underneath, and let try-with-resources run close.

A is a character stream in front of another Writer. The is what makes many small writes cheap. It is also why a write can succeed in the program and still be absent from the file.

Write text, name the charset

Files.newBufferedWriter opens the file, encodes with the you pass, and puts a buffer in front. UTF-8 is the charset to pass. The form that omits a charset also uses UTF-8. It truncates an existing file, the same way a FileOutputStream opened without append does.

WriteLines.java
Path path = Path.of("output.txt");
try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
    writer.write("Hello");
    writer.newLine();
    writer.write("Welcome to Java I/O");
    writer.newLine();
}

write does not add a line ending. writes the separator for the machine you are on. The bytes hit the file only after the buffer is pushed. In this block, that push is the close at the end of the try.

The longhand stack is the same three layers as a buffered read, pointed outward. The file stream takes bytes. The output-stream writer encodes characters with UTF-8. The buffer gathers the characters.

WriteLinesLonghand.java
try (BufferedWriter writer = new BufferedWriter(
        new OutputStreamWriter(new FileOutputStream("output.txt"), StandardCharsets.UTF_8))) {
    writer.write("Hello");
    writer.newLine();
}

Why a crash waits until flush

write("saved") copies five characters into the array and returns. The default array holds 8192 characters, so this call does not fill it, and the wrapped writer is not invoked. The file can still be empty. Any reader looking at the file, including you, sees the previous contents.

is the moment those characters are handed down. close() flushes and then closes the wrapped writer. If the disk is full, the pipe has closed, or the underlying stream throws for any other reason, the IOException comes from that push. The stack trace points at flush or close, and the earlier write has already returned as if the line were stored.

A write long enough to fill the array flushes partway through write, so a large write can throw on the call itself. The case that stays quiet is the short one. Leaving the writer unclosed drops the array on the floor: the characters were accepted, the method that wrote them has moved on, and the file never changes. Killing the process before flush does the same thing with no exception, because the failure never gets a chance to be thrown. The buffer hid the crash by postponing the real write.

FlushEarly.java
Path path = Path.of("status.txt");
try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
    writer.write("Starting job");
    writer.newLine();
    writer.flush();
    runJob();
    writer.write("Job complete");
    writer.newLine();
}

The explicit flush makes "Starting job" visible to anything reading the file while runJob is still going. It also means a full disk is reported there, next to the line that filled the buffer, instead of later at close. The second line still waits for the close at the end of the try.

Who closes

is what runs close, and close is what flushes a buffer you never flushed yourself. Own the BufferedWriter. Its close closes the OutputStreamWriter, which closes the FileOutputStream and releases the descriptor.

Declare that writer last if the file stream is a separate resource in the same header, so the writer closes first while the file can still take the bytes. A method that received an open writer should write and return. Closing a buffer you wrapped around someone else's stream flushes and closes their handle at your return, which is ownership you were not given.

flush on a bare FileOutputStream is a different, empty operation. There is no Java character array there. The delay described on this page starts when this buffer is in the chain. Pushing the array hands bytes to the operating system. It still does not force those pages onto the physical disk.

write("saved") returned, the disk filled, and you have not called flush or close. What has been thrown, and what is in the file?

Nothing has been thrown. Five characters fit in the buffer, so the wrapped writer was never called and never had a chance to fail. The file does not contain "saved" yet. The IOException appears when the buffer is pushed: flush, a later write that fills the array, or close. If the process is killed instead, the line disappears with the buffer and there is still no exception.