Course – LS – All

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

>> CHECK OUT THE COURSE

1. Overview

InputStream and OutputStream are two fundamental classes in Java IO. Sometimes, we need to convert between these two stream types. In an earlier tutorial, we’ve talked about writing InputStream to OutputStream.

In this quick tutorial, we’ll look in the opposite direction. We’ll explore how to convert an OutputStream into an InputStream.

2. Introduction to the Problem

Sometimes, it’s necessary to convert an OutputStream to an InputStream. This conversion can be helpful in various situations, such as when we need to read data that has been written to an OutputStream.

In this article, we’ll explore two different ways to perform this conversion:

  • Using a byte array
  • Using a pipe

For simplicity, we’ll use ByteArrayOutputStream as the OutputStream type in our examples. Also, we’ll use unit test assertions to verify whether we can read expected data from the converted InputStream object.

So next, let’s see them in action.

3. Using a byte Array

When we think about this problem, the most straightforward approach may be:

Next, let’s implement the idea as a test and check if it works as expected:

@Test
void whenUsingByteArray_thenGetExpectedInputStream() throws IOException {
    String content = "I'm an important message.";
    try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
        out.write(content.getBytes());
        try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray())) {
            String inContent = new String(in.readAllBytes());

            assertEquals(content, inContent);
        }
    }
}

First, we’ve prepared an OutputStream object (out) and written a string (content) to it. Next, we get the data as a byte array from the OutputStream by calling out.toByteArray() and create an InputStream from the array.

If we run the test, it passes. So the conversion is successful.

It’s worth mentioning that we’ve used try-with-resources to ensure the InputStream and OutputStream are closed after the read and write operations. Also, our try blocks don’t have catch blocks as we’ve declared the test method throwing IOException.

This approach is simple and easy to implement. However, the disadvantage is that it requires storing the entire output in memory before it can be read back as input. In other words, if the output is very large, it could lead to significant memory consumption and potentially cause OutOfMemoryError.

4. Through a Pipe

We often use the PipedOutputStream and PipedInputStream classes together to allow data to be passed from an OutputStream to an InputStream. Therefore, we can first connect a PipedOutputStream and a PipedInputStream, so that the PipedInputStream can read data coming from the PipedOutputStream. Next, we can write the given OutputStream‘s data to the PipedOutputStream. Then, on the other side, we can read the data from the PipedInputStream.

Next, let’s implement this as a unit test:

@Test
void whenUsingPipeStream_thenGetExpectedInputStream() throws IOException {
    String content = "I'm going through the pipe.";

    ByteArrayOutputStream originOut = new ByteArrayOutputStream();
    originOut.write(content.getBytes());

    //connect the pipe
    PipedInputStream in = new PipedInputStream();
    PipedOutputStream out = new PipedOutputStream(in);

    try (in) {
        new Thread(() -> {
            try (out) {
                originOut.writeTo(out);
            } catch (IOException iox) {
                // ...
            }
        }).start();

        String inContent = new String(in.readAllBytes());
        assertEquals(content, inContent);
    }
}

As the code above shows, first, we prepared the OutputStream (originOut). Next, we created a PipedInputStream (in) and a PipedOutputStream (out) and connected them. Thus, a pipe is established.

Then, we forwarded the data from the given OutputStream to the PipedOutputStream using the ByteArrayOutputStream.writeTo() method. We should note that we created a new thread to write data to the PipedOutputStream. This is because using both PipedInputStream and PipedOutputStream objects from the same thread isn’t recommended. This may lead to a deadlock.

Finally, the test passes if we execute it. So the OutputStream is converted to a PipedInputStream successfully.

5. Conclusion

In this article, we’ve explored two approaches to converting an OutputStream to an InputStream:

  • byte array as a buffer – this is straightforward. However, it has the potential risk of OutOfMemoryError
  • Using a pipe – writing the output to the PipedOutputStream makes data flow to the PipedInputStream

As usual, all code snippets presented here are available over 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)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.