Redirect console output to string in Java
Asked Answered
C

5

84

I have one method whose return type is void and it prints directly on console.

However I need that output in a String so that I can work on it.

As I can't make any changes to the method with return type void I have to redirect that output to a String.

How can I redirect it in Java?

Cullan answered 3/1, 2012 at 5:34 Comment(0)
D
156

If the function is printing to System.out, you can capture that output by using the System.setOut method to change System.out to go to a PrintStream provided by you. If you create a PrintStream connected to a ByteArrayOutputStream, then you can capture the output as a String.

Example:

// Create a stream to hold the output
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
// IMPORTANT: Save the old System.out!
PrintStream old = System.out;
// Tell Java to use your special stream
System.setOut(ps);
// Print some output: goes to your special stream
System.out.println("Foofoofoo!");
// Put things back
System.out.flush();
System.setOut(old);
// Show what happened
System.out.println("Here: " + baos.toString());

This program prints just one line:

Here: Foofoofoo!
Decennium answered 3/1, 2012 at 5:37 Comment(7)
Don't forget, when you are done, to call System.out.flush(), and then switch System.out back to the normal (or, more correctly, the previous) System.out. I see that @Ernest has added that into his code.Capers
Also, don't forget that this creates threading issues, not just for this method (which you could solve by synchronizing it) but for any other code that prints to stdout. baos.toString() could easily be "Foofohello worldofoo!"Cassady
@tuskiomi The string comes from ByteArrayOutputStream.toString() -- see last line of sample code.Decennium
@ErnestFriedman-Hill :- I have a c utility which returns some value based on options. I executed this utility using java and the output is on the console, how to read this console echo output. there is no stream associated with the output. because I use a redirect to CON: ex:- util.exe -o CON: ( CON: for console output in DOS )Handbill
Hi, what if the function is printing to c++ cout? Like for my case, my java application calls my c++ application methods thorough jni wrapper. Then how is this can be done @ErnestFriedman-HillHerculie
If I'm printing 3 times instead of 1, do I need to call System.out.flush 3 times?Tenenbaum
@Tenenbaum Nope. Just right before you stop using the other stream.Decennium
K
29

Here is a utility Class named ConsoleOutputCapturer. It allows the output to go to the existing console however behind the scene keeps capturing the output text. You can control what to capture with the start/stop methods. In other words call start to start capturing the console output and once you are done capturing you can call the stop method which returns a String value holding the console output for the time window between start-stop calls. This class is not thread-safe though.

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.List;

public class ConsoleOutputCapturer {
    private ByteArrayOutputStream baos;
    private PrintStream previous;
    private boolean capturing;

    public void start() {
        if (capturing) {
            return;
        }

        capturing = true;
        previous = System.out;      
        baos = new ByteArrayOutputStream();

        OutputStream outputStreamCombiner = 
                new OutputStreamCombiner(Arrays.asList(previous, baos)); 
        PrintStream custom = new PrintStream(outputStreamCombiner);

        System.setOut(custom);
    }

    public String stop() {
        if (!capturing) {
            return "";
        }

        System.setOut(previous);

        String capturedValue = baos.toString();             

        baos = null;
        previous = null;
        capturing = false;

        return capturedValue;
    }

    private static class OutputStreamCombiner extends OutputStream {
        private List<OutputStream> outputStreams;

        public OutputStreamCombiner(List<OutputStream> outputStreams) {
            this.outputStreams = outputStreams;
        }

        public void write(int b) throws IOException {
            for (OutputStream os : outputStreams) {
                os.write(b);
            }
        }

        public void flush() throws IOException {
            for (OutputStream os : outputStreams) {
                os.flush();
            }
        }

        public void close() throws IOException {
            for (OutputStream os : outputStreams) {
                os.close();
            }
        }
    }
}
Karikaria answered 5/6, 2015 at 11:23 Comment(1)
Really nice solution, though I would suggest one little addition, call the close() method on baos stream in stop() method just before setting it to null to release all the resources.Progress
K
2

If you are using Spring Framework, there is a really easy way to do this with OutputCaptureExtension:

 @ExtendWith(OutputCaptureExtension.class)
 class MyTest {

     @Test
     void test(CapturedOutput output) {
         System.out.println("ok");
         assertThat(output).contains("ok");
         System.err.println("error");
     }

     @AfterEach
     void after(CapturedOutput output) {
         assertThat(output.getOut()).contains("ok");
         assertThat(output.getErr()).contains("error");
     }

 }
Kliman answered 23/9, 2021 at 14:0 Comment(0)
H
0

Although this question is very old and has already very good answers I want to provide an alternative. I created a library specifically for this use case. It is called Console Captor and you can add it with the following snippet:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>consolecaptor</artifactId>
    <version>1.0.0</version>
    <scope>test</scope>
</dependency>

Example class

public class FooService {

    public void sayHello() {
        System.out.println("Keyboard not responding. Press any key to continue...");
        System.err.println("Congratulations, you are pregnant!");
    }

}

Unit test

import static org.assertj.core.api.Assertions.assertThat;

import nl.altindag.console.ConsoleCaptor;
import org.junit.jupiter.api.Test;

public class FooServiceTest {

    @Test
    public void captureStandardAndErrorOutput() {
        ConsoleCaptor consoleCaptor = new ConsoleCaptor();

        FooService fooService = new FooService();
        fooService.sayHello();

        assertThat(consoleCaptor.getStandardOutput()).contains("Keyboard not responding. Press any key to continue...");
        assertThat(consoleCaptor.getErrorOutput()).contains("Congratulations, you are pregnant!");
        
        consoleCaptor.close();
    }
}
Howlett answered 31/7, 2021 at 14:56 Comment(0)
W
0

Here is my full solution which is based on the same API as @Ernest Friedman-Hill mentioned.

All may be done with single line:

String capturedOut = ISystemOutAcquire.acquire(MyClass::myStaticFunOrRunnable);
  • Below solution allows passing any Runnable and acquire the System.out during the Runnable.run() method call. Of course it doesn't guarantee that another thread will not write additional data at the same time.
  • The original System.out is restored when ISystemOutAcquire.close() is called - the RAII-like behavior.

Unit test:

package pl.mjaron.etudes;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class ISystemOutAcquireTest {

    static void printingCode() {
        System.out.print("Sample output.");
    }

    @Test
    void acquireCall() {
        final String contentFromCall = ISystemOutAcquire.acquire(ISystemOutAcquireTest::printingCode);
        assertEquals("Sample output.", contentFromCall);
    }

    @Test
    void acquireScope() {

        final String contentFromScope;
        try (ISystemOutAcquire acquire = ISystemOutAcquire.acquire()) {
            printingCode();
            contentFromScope = acquire.toString();
        }
        assertEquals("Sample output.", contentFromScope);
    }
}

The implementation:

package pl.mjaron.etudes;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

public interface ISystemOutAcquire extends AutoCloseable {

    PrintStream getOriginalOut();

    PrintStream getNewOut();

    /**
     * Restores the original stream.
     */
    @Override
    void close();

    /**
     * Replaces the original {@link PrintStream} with another one.
     */
    class ToPrintStream implements ISystemOutAcquire {
        private final PrintStream originalOut;
        private final PrintStream newOut;

        public ToPrintStream(PrintStream newOut) {
            this.originalOut = System.out;
            this.newOut = newOut;
            originalOut.flush();
            newOut.flush();
            System.setOut(newOut);
        }

        @Override
        public void close() {
            originalOut.flush();
            newOut.flush();
            System.setOut(originalOut);
        }

        @Override
        public PrintStream getOriginalOut() {
            return originalOut;
        }

        @Override
        public PrintStream getNewOut() {
            return newOut;
        }
    }

    public static ToPrintStream acquire(final PrintStream newOut) {
        return new ToPrintStream(newOut);
    }

    /**
     * Captures the {@link System#out} to {@link ByteArrayOutputStream}.
     * <p>
     * Overrides the {@link #toString()} method, so all captured text is accessible.
     */
    class ToByteArray extends ToPrintStream {

        private final ByteArrayOutputStream byteArrayOutputStream;

        public ToByteArray(final ByteArrayOutputStream byteArrayOutputStream) {
            super(new PrintStream(byteArrayOutputStream));
            this.byteArrayOutputStream = byteArrayOutputStream;
        }

        @Override
        public String toString() {
            return byteArrayOutputStream.toString();
        }

        public ByteArrayOutputStream getByteArrayOutputStream() {
            return byteArrayOutputStream;
        }
    }

    static ToByteArray acquire(final ByteArrayOutputStream byteArrayOutputStream) {
        return new ToByteArray(byteArrayOutputStream);
    }

    static ToByteArray acquire() {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        return acquire(byteArrayOutputStream);
    }

    static String acquire(final Runnable runnable) {
        try (ISystemOutAcquire acquire = ISystemOutAcquire.acquire()) {
            runnable.run();
            return acquire.toString();
        }
    }
}

Way answered 7/3, 2023 at 15:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.