How do you programmatically redirect System.out (or System.err) inside a running JVM, and how do you safely restore it?
answer
- setIn / setOut / setErr
- save original, restore in finally
- final fields, native setter
- ByteArrayOutputStream to capture
- explicit charset + autoFlush
- global = process-wide, not per-thread
basics
~10 sCall System.setOut(new PrintStream(...)) with your own PrintStream (for example one wrapping a ByteArrayOutputStream or a file). Save the original first and restore it afterward with System.setOut(original).
solid answer
~40 sSystem.out, System.err and System.in are fields you can replace at runtime via System.setOut(PrintStream), System.setErr(PrintStream) and System.setIn(InputStream). Even though the fields are final, the JVM uses native setter methods to mutate them. To redirect, save the current stream (PrintStream original = System.out), install your own (e.g. new PrintStream(new ByteArrayOutputStream(), true, StandardCharsets.UTF_8) to capture, or one wrapping a FileOutputStream to log to a file), do the work, then always restore the original in a finally block. Restoration matters because the change is global and process-wide: every class sees the new System.out, so leaving it swapped breaks unrelated code and other tests. Specify the charset and consider auto-flush when constructing the PrintStream.
code
java · 14 linesimport java.io.*;
import java.nio.charset.StandardCharsets;
String captureOutput(Runnable task) {
PrintStream original = System.out;
var buffer = new ByteArrayOutputStream();
try {
System.setOut(new PrintStream(buffer, true, StandardCharsets.UTF_8));
task.run();
} finally {
System.setOut(original); // restore even if task throws
}
return buffer.toString(StandardCharsets.UTF_8);
}go deeper
Know that System.setOut(new PrintStream(...)) replaces output and that you should put the original back.
Show the full save/install/restore-in-finally pattern, capture via ByteArrayOutputStream, and pass an explicit charset.
Explain why the final fields are mutable (native setters), flushing pitfalls, and that the swap is global/not thread-safe — so prefer it only for tests or single-threaded redirection.
Weigh programmatic redirection against shell-level redirection and logging frameworks; note that mutating global state is a smell, and recommend dependency-injected streams or a logging abstraction for production code.
## The setters Java lets you swap the standard streams while the program runs: - `System.setIn(InputStream in)` - `System.setOut(PrintStream out)` - `System.setErr(PrintStream err)` A common source of confusion: `System.out`/`in`/`err` are declared `public static final`, yet these setters change them. They work because the setters are **native** methods that bypass the normal `final` rule — the language guarantee is relaxed specifically for these three fields. You cannot reassign `System.out = x` yourself in code; you must go through the setter. ## Building a replacement PrintStream `setOut` needs a `PrintStream`. You construct one over any `OutputStream`: - **Capture in memory:** `new PrintStream(new ByteArrayOutputStream(), /*autoFlush*/ true, StandardCharsets.UTF_8)` — then read the captured text from the ByteArrayOutputStream with `toString(charset)`. - **Write to a file:** `new PrintStream(new FileOutputStream("app.log"), true, StandardCharsets.UTF_8)`. Always pass an explicit **charset** (the 3-arg constructor). The no-charset constructor uses the platform default, which makes captured/redirected text non-portable. ## The save / restore pattern (mandatory) Because `System.out` is a single global field, replacing it affects the **entire process** — every thread, every library. You must restore it: ```java PrintStream original = System.out; var buffer = new ByteArrayOutputStream(); try { System.setOut(new PrintStream(buffer, true, StandardCharsets.UTF_8)); runCode(); // its output now goes to buffer } finally { System.setOut(original); // always restore, even on exception } String captured = buffer.toString(StandardCharsets.UTF_8); ``` The `finally` block is non-negotiable: if `runCode()` throws and you skip restoration, the rest of the program (and subsequent tests) loses its console. ## Flushing If you capture into a buffer and read it before the PrintStream auto-flushes, you may see partial output. Either construct the PrintStream with `autoFlush = true`, or call `System.out.flush()` before reading the buffer. ## Thread-safety caveat The swap is global and not scoped to the current thread. If another thread writes to `System.out` during your redirect window, its output also lands in your buffer. This is a key limitation: programmatic redirection is fine for single-threaded code and isolated tests, but is racy in concurrent contexts. ## First-principles takeaway Redirection is nothing more than pointing a global field at a different sink. The discipline — save original, set replacement, restore in finally, pick a charset, mind flushing and concurrency — exists because that field is shared by the whole JVM.
- System.out is declared 'public static final' — how can System.setOut change it?setOut/setErr/setIn are native methods that the JVM permits to mutate these specific final fields. Ordinary Java code cannot reassign the field directly; it must call the setter.
- Why might captured output be empty or truncated when you read the buffer immediately?The replacement PrintStream may still be buffering. Construct it with autoFlush=true or call flush() before reading the underlying ByteArrayOutputStream.
- What goes wrong if you forget to restore the original stream?The change is process-wide, so all later code and other tests keep writing to your (possibly closed) buffer/file, losing or corrupting their output. Hence the finally-block restore.
saying these in an interview costs you the question
- Reassigning System.out = ... directly (won't compile — must use setOut)
- Not restoring the original stream in a finally block
- Using the platform default charset instead of an explicit one
- Assuming redirection is scoped to the current thread
- Reading the buffer before flushing