Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

Printing output to the console for debugging or displaying information to the user is common. However, sometimes, it may be necessary to save the console output to a text file for further analysis or documentation purposes.

In this tutorial, we’ll explore how to redirect console output to a text file in Java.

2. Preparation

When we talk about writing console output to a text file, there could be two scenarios:

  • File only – Redirect all output to a file. No output will be printed to the console.
  • Console and File – Output is written to the console and a file.

We’ll cover both cases in this tutorial.

Before we move to the coding part, let’s prepare some text to write to the console. To make it easier to test, let’s put three lines in a string list:

final static List<String> OUTPUT_LINES = Lists.newArrayList(
  "I came",
  "I saw",
  "I conquered");

Later, we’ll discuss how to redirect the output from the console to a file. So, we’ll use unit test assertions to verify if the file contains the expected content.

JUnit 5’s temporary directory feature allows us to create a file, write data to the file, and remove the file automatically after the verification. Therefore, we’ll use it in our tests.

Next, let’s see how to make the redirection happen.

3. Output Only Goes to the File

Usually, we use System.out.println() to print text to the console. System.out is a PrintStream, the standard output by default. The System class provides the setOut() method that allows us to replace the default “out” with one of our PrintStream objects.

Since we want to write data to a file, we can create a PrintStream from a FileOutputStream.

Next, let’s create a test method to see if this idea works:

@Test
void whenReplacingSystemOutPrintStreamWithFileOutputStream_thenOutputsGoToFile(@TempDir Path tempDir) throws IOException {
    PrintStream originalOut = System.out;
    Path outputFilePath = tempDir.resolve("file-output.txt");
    PrintStream out = new PrintStream(Files.newOutputStream(outputFilePath), true);
    System.setOut(out);

    OUTPUT_LINES.forEach(line -> System.out.println(line));
    assertTrue(outputFilePath.toFile().exists(), "The file exists");
    assertLinesMatch(OUTPUT_LINES, Files.readAllLines(outputFilePath));
    System.setOut(originalOut);
}

In the test, we first back up the default System.out. Then, we construct a PrintStream out, which wraps a FileOutputStream. Next, we replace the default System.out with our “out“.

These operations make System.out.println() print data to the file file-output.txt instead of the console. We’ve verified the file content using the assertLinesMatch() method.

Finally, we restore the System.out back to the default one.

If we run the test, it passes. Further, no output is printed to the console.

4. Creating a DualPrintStream Class

Now, let’s see how to print data to the console and a file. In other words, we need two PrintStream objects.

Since System.setOut() only accepts one PrintStream parameter, we cannot pass two PrintStreams. However, we can create a new PrintStream subclass to carry one extra PrintStream object:

class DualPrintStream extends PrintStream {
    private final PrintStream second;

    public DualPrintStream(OutputStream main, PrintStream second) {
        super(main);
        this.second = second;
    }
    
    ...
}

The DualPrintStream extends PrintStream. Further, we can pass an extra PrintStream object (second) to the constructor. Then, we must override PrintStream‘s write() method so that the second PrintStream can piggyback on the same and apply the same operation:

class DualPrintStream extends PrintStream {
    ...
    
    @Override
    public void write(byte[] b) throws IOException {
        super.write(b);
        second.write(b);
    }
}

Now, let’s check if it works as expected:

@Test
void whenUsingDualPrintStream_thenOutputsGoToConsoleAndFile(@TempDir Path tempDir) throws IOException {
    PrintStream originalOut = System.out;
    Path outputFilePath = tempDir.resolve("dual-output.txt");
    DualPrintStream dualOut = new DualPrintStream(Files.newOutputStream(outputFilePath), System.out);
    System.setOut(dualOut);
    OUTPUT_LINES.forEach(line -> System.out.println(line));
    assertTrue(outputFilePath.toFile().exists(), "The file exists");
    assertLinesMatch(OUTPUT_LINES, Files.readAllLines(outputFilePath));
    System.setOut(originalOut);
}

It passes when we give it a run, which means the text has been written to the file. Also, we can see the three lines in the console.

Finally, It’s worth noticing that the PrintStream has other methods that we need to override to keep the File and Console Streams in sync, such as close(), flush(), and other variants of write(). We should also override the checkError() method to manage IOExceptions gracefully.

5. Conclusion

In this article, we’ve learned to make System.out.println() print data to a file by replacing the default System.out.

As usual, all code snippets presented here are available on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are closed on this article!