← Runtimes
Runtime walkthrough 7 questions · 6 languages

I/O, buffering, and external state

Printed text appears at once in most programs. This lesson shows where text can wait before it appears.

System.out sends its text out at every println, so its lines appear when they are printed. A BufferedWriter you add keeps text in memory until you call flush or close, or it is full: not at a newline, and not at the end of the program.

Before you begin

Why this matters

Most of the time output appears when you print it, so the times it does not are confusing: lines in the wrong order, or the end of a report missing because a writer was never closed. In Java these come from writers you add. Once you know where text waits, you can predict them and fix them.

What this helps you reason about

  • Predict whether a message is on the screen before a slow step runs.
  • Explain why two messages can appear in a different order from the code that wrote them.
  • Spot text that will be lost if the program stops early, and send it out in time.

When you call System.out.println, you see the line at once, and in Java that model is correct. It is a deliberate choice: System.out flushes itself at every println. This lesson asks what happens when output goes through a buffer that does not, such as a BufferedWriter, and what happens to waiting text when a program exits early.

Already know this? The short version
  • System.out and System.err are PrintStreams with automatic flushing: the documentation promises a flush at println, at a newline, and at a byte-array write, wherever output goes; OpenJDK 17 also flushes after print, but call flush() after a prompt to be safe.
  • A BufferedWriter (8192 characters by default) is flushed only by flush(), close(), or filling up; never at a newline and never at the end of main.
  • System.exit runs shutdown hooks but flushes no writer; unlike C’s exit, nothing in the JVM flushes your buffers for you.
  • Close writers with try-with-resources, flush before writing to System.err when order matters, and do not close a writer that wraps System.out if you will print again.
  • flush hands bytes to the operating system; FileChannel.force asks for a disk write.

Go to the final check· Go to the practice tickets

Words in this lesson (8)
operating system
The software, such as Linux, macOS, or Windows, that runs programs and controls the screen, files, and other devices. Every write a program makes is a request to it.
stream
A destination that receives bytes in order. A program writes to streams; it does not draw on the screen itself.
standard output and standard error
The two output streams every program starts with. Standard output is for normal results; standard error is for messages about problems.
terminal and pipe
A terminal is the window where you type commands. A pipe is a connection that carries one program’s output into another program’s input.
buffer
An area of memory where written text waits inside the program before it is sent on to the operating system.
BufferedWriter
A Java writer that keeps 8192 characters by default and passes them on only when you call flush or close, or when it is full.
flush
To send the text waiting in a buffer on to the operating system now. A flush is not a disk write.
System.exit
Stops the JVM. It runs shutdown hooks, but it flushes no writer.

Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.

01 / A write call

What does a print call promise?

Here is a useful model: System.out.println writes to the screen immediately. For most small programs, it predicts exactly what you see.

Underneath, a program does not draw on the screen itself. It asks the operating system to write bytes to a stream, and the operating system delivers them.

Every program starts with two output streams. Standard output is for the program’s normal results; System.out writes there. Standard error is for messages about problems; System.err writes there.

The program does not choose where these streams go. Whoever starts the program chooses. A stream may go to a terminal, to a file, or through a pipe into another program.

Main.java
System.out.print("loading... "); // no newline
int total = 0;
for (int i = 1; i <= 3; i++) {
    total += i;
}
System.out.println("ready " + total);

A write passes text to a stream. The program does not choose where that stream goes.

Coming from Python: Python’s print is block-buffered through a pipe; Java’s System.out flushes at every println wherever output goes.

Precisely: System.out’s own buffer

In OpenJDK 17, System.out is a PrintStream around a 128-byte BufferedOutputStream, created with automatic flushing. The PrintStream documentation promises a flush when a byte array is written, after println, and when a newline is written. It describes print(String) as writing bytes the way write(int) does, which flushes only at a newline. So the guarantee covers println, not print without a newline. Observed in OpenJDK 17 (in the stepper, and in its PrintStream code): print converts its characters through an internal writer that passes them to the stream as a byte array, so print flushes too, and loading... appeared before the loop. Another Java implementation may hold the partial line, so a prompt printed with print should be followed by System.out.flush().

Experiment

Watch when each write reaches the output panel

System.out is a PrintStream created with automatic flushing. Its documentation promises a flush at println, at a newline, and when a byte array is written. In OpenJDK 17, print also passes its characters on as a byte array, so even this partial line left at once in the stepper. Call System.out.flush() after a prompt if you need that guarantee everywhere.

Why it matters Before you assume "it prints immediately", ask what sits between your print call and the screen. When nothing does, output appears in statement order.

Predict whether loading... appears before the loop runs, even though System.out.print adds no newline. Then change print to println and check whether the timing changes.

class Main {
    public static void main(String[] args) {
        System.out.print("loading... ");
        int total = 0;
        for (int i = 1; i <= 3; i++) {
            total += i;
        }
        System.out.println("ready " + total);
    }
}
What to look for

Step past the System.out.print line and look at the output panel: loading... is already there while the loop runs. The panel shows standard output as the stepper received it at each stop.

Expected output

loading... ready 6

02 / Buffers

Where does written text wait?

Asking the operating system to write takes time, much more than copying characters in memory. So programs often collect text in a buffer first: an area of memory where written text waits until there is enough to send at once.

A BufferedWriter is such a buffer. It holds 8192 characters by default and passes them on only when flushed, closed, or full. Programs that print many lines use one for speed.

So the model from section 01, that a print writes to the screen immediately, needs one addition: written text can still be inside the program. Anything written straight to System.out can arrive first.

Main.java
BufferedWriter out =
    new BufferedWriter(new OutputStreamWriter(System.out));
out.write("1 queued\n"); // waits in out
System.out.println("2 direct"); // goes straight out
out.flush();

A buffer holds written text inside the program. Until it is sent on, nothing outside the program can see it.

Precisely: PrintWriter

new PrintWriter(writer) does not flush automatically. With true as the second argument it flushes only on println, printf, and format. A PrintWriter around a BufferedWriter therefore holds text like the BufferedWriter does.

Experiment

Make a direct write arrive before a buffered one

BufferedWriter keeps characters in its own buffer until flush, close, or a full buffer. System.out.println goes straight through System.out, which flushes after every println, so it does not wait for out.

Why it matters When text reaches one output by two paths and only one path is buffered, lines can come out in a different order from the code. Flush the buffered path before writing on the other one.

Predict the order of the three lines. Then move the first out.flush() above System.out.println("2 direct") and predict again.

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedWriter out =
            new BufferedWriter(new OutputStreamWriter(System.out));
        out.write("1 queued\n");
        System.out.println("2 direct");
        out.flush();
        out.write("3 queued\n");
        out.flush();
    }
}
What to look for

Watch the output panel at each stop. 2 direct appears first, at the stop after it is written. 1 queued appears only at the stop after the flush, even though it was written first. After the edit, 1 queued appears first, and the lines come out in code order. If you open out in the variables panel, its cb array shows the characters one by one: '1', ' ', 'q', and so on. flush does not clear cb, so the old characters stay on screen after the flush; nextChar, the number of characters still waiting, goes back to 0. The number after @, as in java.io.BufferedWriter@48, is the debugger’s ID for the object, not an address.

Expected output

2 direct
1 queued
3 queued

03 / Flushing

What moves text out of a buffer?

To flush a buffer is to send the text waiting in it on to the operating system now. Buffers are flushed in four common ways. The program asks for it: an explicit flush. A newline is written: line buffering. The buffer is full and needs room. And some buffers, in some languages, are flushed when the program exits normally. Which of them apply depends on the language and on the buffer.

A BufferedWriter uses only some of them: an explicit flush, a full buffer, and close, which flushes first. It never flushes at a newline, and nothing flushes it when the program ends. try-with-resources closes it at the end of the block.

Main.java
Path report = Path.of("report.txt");
try (BufferedWriter out = Files.newBufferedWriter(report)) {
    out.write("first\n");
    out.write("second\n");
} // close() flushes here

Flushing moves text from a buffer to the operating system. A BufferedWriter is flushed only by flush, close, or filling up.

Coming from C: In C, exit flushes stdio buffers when the program ends; in Java, neither System.exit nor the end of main flushes a BufferedWriter.

Precisely: closing System.out

Closing a writer that wraps System.out also closes System.out, and later prints are silently dropped. Flush such a writer instead of closing it if you will print again. A flush hands bytes to the operating system; FileChannel.force asks it to write a file to the disk.

04 / Terminals and pipes

Why can the same program print differently in a terminal?

Many buffers change their behavior depending on where output goes. A person at a terminal wants to see each line as soon as it is printed. A program reading from a pipe, or a file being written, does better with fewer, larger writes. So a common choice is line buffering for a terminal and full buffering for a pipe or a file. A fully buffered stream is flushed only when its buffer fills up or when the program asks.

Java does not make this choice: System.out flushes at every println whether it goes to a terminal, a pipe, or a file, and a BufferedWriter buffers fully in all three. System.console() returns null when the program is not connected to a terminal.

Main.java
if (System.console() != null) {
    System.out.println("running in a terminal");
} else {
    System.out.println("not connected to a terminal");
}

Where output goes does not change Java’s buffering, but it does in other languages. Do not assume a terminal.

Precisely: the stepper is not a terminal

The stepper runs Java under a debugger (JDI) that reads standard output through a pipe, so System.console() returns null there. Output timing is the same either way, because System.out flushes at every println.

05 / Two streams

Why do errors and results come out of order?

Standard output and standard error are separate streams, so they can go to different places. Whoever starts a program can send its standard output to a file while standard error still appears in the terminal. That is why results go to standard output and problems to standard error: a program that reads the results never sees the warnings.

The two streams can also go to the same file. Then the order of lines in that file depends on buffering. If standard output is buffered and standard error is not, a warning can appear in the file before results that the program wrote earlier.

System.out and System.err both flush on every println, so whole lines keep their order even in one file. Once results go through a BufferedWriter, a warning on System.err can appear before them. Flush the writer before writing to System.err.

Main.java
for (String value : values) {
    if (isValid(value)) {
        out.write(convert(value) + "\n");
    } else {
        out.flush(); // earlier results first
        System.err.println("warning: skipped " + value);
    }
}
out.flush();

Results go to standard output, problems to standard error. Flush buffered results before writing to the other stream.

Precisely: redirecting in a terminal, and the output panel

In a terminal, tool > results.txt runs a program named tool, and > sends its standard output to the file results.txt. tool > run.log 2>&1 sends both streams to run.log. Read 2>&1 one piece at a time: 2 is the number of standard error, > sends it somewhere, and &1 means stream 1, standard output, not a file named 1. So 2>&1 means “send standard error where standard output goes”. The stepper collects standard error separately and does not show it. The buffers experiment uses System.out.println as the unbuffered path; System.err behaves the same way.

06 / Lost output

What happens to waiting text when a program stops early?

Text in a buffer is still inside the program. If the program stops before the buffer is flushed, that text is gone. This happens when a program crashes, when the operating system stops it, and when it ends through a call that skips the normal cleanup.

In Java, System.exit stops the JVM. It runs shutdown hooks, but it does not flush a BufferedWriter. Reaching the end of main does not flush it either: the JVM never flushes writers for you. A crash of the JVM, or the operating system killing it, also loses whatever is buffered.

Main.java
BufferedWriter out =
    new BufferedWriter(new OutputStreamWriter(System.out));
out.write("saved\n");
out.flush();
out.write("lost\n"); // never flushed
System.exit(0);

Buffered text survives only if something flushes it. Close or flush writers where you finish with them.

Precisely: shutdown hooks

Runtime.getRuntime().addShutdownHook registers a thread that runs during System.exit and normal termination, and it could flush a writer. Hooks do not run when the process is killed with SIGKILL or the JVM crashes, so closing writers where you finish with them is more reliable.

Experiment

Exit while text is still in a buffer

System.exit stops the JVM without flushing a BufferedWriter. Reaching the end of main does not flush it either: only flush or close moves the text on, and nothing calls them.

Why it matters Close a BufferedWriter when you are done with it, for example with try-with-resources. The end of main is not a flush.

Predict whether lost is printed. Then delete System.exit(0) and predict again: does reaching the end of main print it?

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedWriter out =
            new BufferedWriter(new OutputStreamWriter(System.out));
        out.write("saved\n");
        out.flush();
        out.write("lost\n");
        System.exit(0);
    }
}
What to look for

Watch the output panel. saved appears at the stop after the flush. lost never appears, not even after the run has ended. After the edit, lost still never appears.

Expected output

saved

07 / Try it

Can you predict what reaches the output?

Predict the output before revealing the answer. Follow out’s buffer and System.out separately.

Main.java
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;

class Example {
    public static void main(String[] args) throws IOException {
        BufferedWriter out =
            new BufferedWriter(new OutputStreamWriter(System.out));
        out.write("one\n");
        System.out.println("two");
        out.flush();
        out.write("three\n");
        System.out.println("four");
    }
}

What does this print? Does every line appear?

Reveal the reasoning

two, one, four. one waits in out’s buffer, and System.out.println prints two at once. out.flush() sends one. three waits in the buffer again, four is printed at once, and main ends without flushing out, so three is lost.

Practice tickets

  1. IO-101 · Show progress while photos copy → Free
  2. IO-102 · Keep the move journal after a crash → Sign in · Pro
  3. IO-103 · Separate results from warnings → Sign in · Pro

Keep exploring

Stack and heap →

Primary references