Convert Reader to InputStream (and Back) in Java

Convert a Reader to InputStream in Java with a String, OutputStreamWriter or Commons IO ReaderInputStream, and back with InputStreamReader and a charset.

Diagram showing a Reader encoded to bytes for an InputStream with a charset, and an InputStream decoded to characters by InputStreamReader

To convert a Reader to an InputStream in Java, we read the characters into a String, encode it with a charset and wrap the bytes in a ByteArrayInputStream; to go from an InputStream to a Reader, we wrap the stream in an InputStreamReader with the same charset. A Reader produces characters and an InputStream produces bytes, so both directions need a charset.

We need Reader to InputStream conversion when text comes from a character source, such as a StringReader, a template engine or a database Clob, and the receiving API accepts only bytes, such as an HTTP upload, a storage client or a hashing method. The opposite direction comes up whenever we read text from a socket, a process or a file stream.

The following example converts a Reader to an InputStream and back again.

Reader reader = new StringReader("apple,5");
String text = reader.readAllAsString();                                         // "apple,5"
InputStream in = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
int byteCount = in.available();                                                 // 7

Reader back = new InputStreamReader(in, StandardCharsets.UTF_8);
String again = back.readAllAsString();                                          // "apple,5"

Notice that readAllAsString() exists since Java 25 and that the charset appears on both sides of the round trip. Older Java versions get their own one-liners, and we also look at a streaming conversion that does not hold the whole text in memory, the opposite direction with InputStreamReader and what goes wrong when the charsets differ.

1. Converting Reader to InputStream

A Reader has no bytes to give, because it only knows characters. To get an InputStream, some code must encode each character into one or more bytes. In UTF-8, the letter a becomes one byte and the accented letter \u00e9 (e with an acute accent) becomes two, so the byte count depends on the charset as well as on the text.

Diagram showing a Reader encoded to bytes for an InputStream with a charset, and an InputStream decoded to characters by InputStreamReader
Converting between characters and bytes always goes through a charset, in both directions

If we already hold the text as a String or a char[], we skip the Reader and call getBytes(StandardCharsets.UTF_8) on the text. The article on converting an InputStream to a String shows that direction for strings.

1.1. Reader to String to InputStream

The simplest conversion reads the whole Reader into a String first. Java offers a different one-liner for that step depending on the version, and the rest is the same for all of them.

Reader reader = new StringReader("banana,3");
String text = reader.readAllAsString();                         // Java 25: "banana,3"

Reader reader10 = new StringReader("banana,3");
StringWriter writer = new StringWriter();
long copied = reader10.transferTo(writer);                      // Java 10: 8

InputStream in = new ByteArrayInputStream(writer.toString().getBytes(StandardCharsets.UTF_8));
String check = new String(in.readAllBytes(), StandardCharsets.UTF_8);   // "banana,3"

The method Reader.transferTo(Writer) (Java 10) copies all characters into any Writer and returns the number of characters. Older code fills a char[] buffer in a loop and appends it to a StringBuilder, which does the same work in eight lines.

Every String-based conversion holds the whole text in memory twice, once as characters and once as bytes. For a config file or a JSON payload that is fine. For a 500 MB export it is not, and the next two sections avoid it.

1.2. Writing the Reader to an OutputStream

Many APIs that seem to need an InputStream also accept an OutputStream to write into, for example a servlet response, a ZipOutputStream entry or a file. In that case we do not need an InputStream at all. An OutputStreamWriter encodes characters into the byte stream, and transferTo() pushes the characters through it in chunks.

Reader report = new StringReader("apple,5\nbanana,3\n");
ByteArrayOutputStream target = new ByteArrayOutputStream();

try (Writer encoder = new OutputStreamWriter(target, StandardCharsets.UTF_8)) {
    report.transferTo(encoder);
}
int written = target.size();   // 17

Closing the writer flushes the last characters into the stream. If the stream must stay open, for example because it belongs to a servlet container, we call encoder.flush() instead of closing the writer.

1.3. Commons IO ReaderInputStream

When the receiving API insists on an InputStream and the text is large, Apache Commons IO offers ReaderInputStream. It reads characters from the Reader as the caller reads bytes, encodes them in small chunks and keeps only a buffer in memory. The JDK has no such class.

Say a reporting service builds a monthly sales CSV with a template engine that writes to a Reader, and the storage client’s upload(InputStream) method sends it to an object store. The ReaderInputStream lets the upload read the report as it is produced.

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.22.0</version>
</dependency>
Reader csv = new StringReader("apple,5\nbanana,3\n");

try (InputStream upload = ReaderInputStream.builder()
        .setReader(csv)
        .setCharset(StandardCharsets.UTF_8)
        .get()) {
    byte[] firstLine = upload.readNBytes(8);
    String head = new String(firstLine, StandardCharsets.UTF_8).strip();   // "apple,5"
}

Since Commons IO 2.12, the builder is the preferred way to create the stream, and the old constructors are deprecated. Closing the ReaderInputStream also closes the Reader. Guava has no streaming equivalent; its CharStreams.toString(reader) only covers the String step from section 1.1.

2. Converting InputStream to Reader

The JDK has a class for this direction. An InputStreamReader reads bytes from the stream and decodes them into characters with the charset we pass. We wrap it in a BufferedReader when we want to read lines.

InputStream in = new ByteArrayInputStream("apple,5\nbanana,3\n".getBytes(StandardCharsets.UTF_8));

List<String> rows;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
    rows = reader.lines().toList();
}
List<String> result = rows;   // [apple,5, banana,3]

Closing the BufferedReader closes the InputStreamReader, which closes the InputStream, so one try-with-resources statement is enough. For files, Files.newBufferedReader(path) creates the same chain in one call and decodes UTF-8 by default. The InputStreamReader guide covers the class in detail.

If we leave out the charset, InputStreamReader uses the default charset, which is UTF-8 since Java 18 (JEP 400). We still name it, because the bytes come from somewhere else, and that source decides the charset, not our JVM.

3. When the Charsets Do Not Match

A conversion that encodes with one charset and decodes with another does not fail. It returns different text, and the bug shows up later as broken names in a database or a wrong hash. The following example encodes the menu item caf\u00e9 (the word cafe with an accented e) in UTF-8 and decodes it with ISO-8859-1.

Reader menu = new StringReader("caf\u00e9");
InputStream in = new ByteArrayInputStream(menu.readAllAsString().getBytes(StandardCharsets.UTF_8));
int bytes = in.available();                                            // 5

Reader wrong = new InputStreamReader(in, StandardCharsets.ISO_8859_1);
String decoded = wrong.readAllAsString();
int decodedLength = decoded.length();                                  // 5
boolean same = decoded.equals("caf\u00e9");                            // false

The word has 4 characters, UTF-8 encodes it in 5 bytes, and ISO-8859-1 turns each of the 5 bytes into its own character. A fixed charset constant such as StandardCharsets.UTF_8 on both sides prevents the problem, and so does a single shared constant in the code that writes and reads the data.

4. Choosing a Conversion

The right approach depends on the direction, the size of the text and what the receiving API accepts.

SituationApproachMemory
Small text, API takes an InputStreamnew ByteArrayInputStream(reader.readAllAsString().getBytes(UTF_8))Whole text twice
Any size, API takes an OutputStreamreader.transferTo(new OutputStreamWriter(out, UTF_8))Buffer only
Large text, API takes an InputStreamCommons IO ReaderInputStream.builder()Buffer only
Bytes to charactersnew InputStreamReader(in, UTF_8)Buffer only
Bytes to linesnew BufferedReader(new InputStreamReader(in, UTF_8))Buffer only

5. Reader and InputStream FAQs

The JDK supports one direction of the conversion much better than the other, and that gap explains most of these questions.

5.1. Why Does the JDK Have No ReaderInputStream?

The JDK designers kept the converter classes in the direction most code needs, bytes to characters for reading and characters to bytes for writing. A Reader to InputStream converter must buffer encoded bytes that the caller has not read yet, and the JDK leaves that to libraries. In practice, writing into an OutputStreamWriter, as in section 1.2, covers most cases.

5.2. Does Closing an InputStreamReader Close the InputStream?

Yes. The method close() of InputStreamReader closes the wrapped stream. If the stream must stay open, such as System.in, we do not close the reader at all.

5.3. How Do I Turn a String Into a Reader?

We create a StringReader, or call Reader.of(text) on Java 24 and later. No charset is involved, because both sides hold characters.

Reader fromString = new StringReader("pear,7");
Reader fromOf = Reader.of(new StringBuilder("pear,7"));
String text = fromOf.readAllAsString();   // "pear,7"

5.4. Is InputStreamReader Slower Than BufferedReader?

For bulk reads they are close, because InputStreamReader reads bytes in chunks internally. The difference shows in loops that call read() once per character, where each call goes through the decoder. BufferedReader adds a character buffer and the readLine() method, so we wrap the reader whenever we process text line by line.

6. Conclusion

A Reader to InputStream conversion in Java always encodes characters into bytes with a charset. For small text, readAllAsString() or transferTo() into a StringWriter plus a ByteArrayInputStream does the job. For large text, we write the Reader into an OutputStreamWriter, or use Commons IO’s ReaderInputStream when the API demands an InputStream.

The way back needs only an InputStreamReader with the same charset. Naming the charset on both sides keeps the text intact on every platform. Other conversions between streams, readers and files are listed in the Java IO tutorial.

7. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.