InputStream to String in Kotlin

1. Overview

In this brief tutorial, we’ll find out how to read an InputStream into a String.

Kotlin provides an easy way to perform the conversion. However, there are still some nuances to consider when working with resources. Plus, we’ll cover special cases, like reading up to a stop character.

2. Buffered Reader

InputStream is an abstraction around an ordered stream of bytes. An underlying data source can be a file, a network connection or any other source emitting bytes. Let’s use a simple file that contains the following data:

Computer programming can be a hassle
It's like trying to take a defended castle

The first solution that we might try is to read the file manually line by line:

val reader = BufferedReader(inputStream.reader())
val content = StringBuilder()
try {
    var line = reader.readLine()
    while (line != null) {
        content.append(line)
        line = reader.readLine()
    }
} finally {
    reader.close()
}

First, we used the BufferedReader class to wrap the InputStream and then read until no lines left in the stream. Furthermore, we surrounded reading logic by the try-finally statement to finally close the stream. Altogether, there’s a lot of boilerplate code.

Could we make it more compact and readable?

Absolutely! At first, we can simplify the snippet by using the readText() function. It reads the input stream completely as a String. Accordingly, we can refactor our snippet as follows:

val reader = BufferedReader(inputStream.reader())
var content: String
try {
    content = reader.readText()
} finally {
    reader.close()
}

However, we still have that try-finally block. Fortunately, Kotlin allows handling resource management in a pseudo-automatic fashion. Let’s look at the next code lines:

val content = inputStream.bufferedReader().use(BufferedReader::readText)
assertEquals(fileFullContent, content)

This one-line solution looks simple, nevertheless, a lot is happening under the hood. One important point in the code above is the call of the use() function. This extension function executes a block on a resource that implements the Closable interface. Finally, when the block is executed Kotlin closes the resource for us.

3. Stop Character

At the same time, there might be a case when we need to read content up to a specific character. Let’s define an extension function for the InputStream class:

fun InputStream.readUpToChar(stopChar: Char): String {
    val stringBuilder = StringBuilder()
    var currentChar = this.read().toChar()
    while (currentChar != stopChar) {
        stringBuilder.append(currentChar)
        currentChar = this.read().toChar()
        if (this.available() <= 0) {
            stringBuilder.append(currentChar)
            break
        }
    }
    return stringBuilder.toString()
}

This function reads bytes from an input stream until a stop character appears. At the same time, in order to prevent the infinite loop, we call the available() method to check whether the stream has any data left. So, if there is no stop character in a stream, then a whole stream will be read.

On the other hand, not all subclasses of the InputStream class provide an implementation for the available() method. Consequently, we have to ensure that the method is implemented correctly before using the extension function.

Let’s get back to our example and read text up to the first whitespace character (‘ ‘):

val content = inputStream.use { it.readUpToChar(' ') }
assertEquals("Computer", content)

As a result, we’ll get the text up to the stop character. In the same way, don’t forget to wrap the block with the use() function to automatically close the stream.

4. Conclusion

In this article, we’ve seen how to convert an InputStream to a String in Kotlin. Kotlin provides a  concise way to work with streams of data, but it’s always worth knowing what is going on internally.

As usual, the implementation of all these examples is over on Github.

Leave a Reply

Your email address will not be published.