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. String to InputStream
So far, we’ve learned how to convert an InputStream to a String in Kotlin. Finally, let’s quickly look at the opposite conversion: String to InputStream.
In Kotlin, we can convert a String to an InputStream using the ByteArrayInputStream class. This class takes a ByteArray as its constructor argument. Therefore, we need to first convert the String to a ByteArray using the toByteArray() function:
val theString = "Kotlin is awesome!"
val inputStream = ByteArrayInputStream(theString.toByteArray())
assertThat(inputStream).hasContent(theString)
It’s worth mentioning that AssertJ allows us to verify InputStream content easily. The hasContent(String) method verifies whether the InputStream instance’s content is equal to the given String.
As the String to InputStream conversion is a pretty common use case, Kotlin’s standard library has wrapped the conversion logic in the String extension byteInputStream():
@kotlin.internal.InlineOnly
public inline fun String.byteInputStream(charset: Charset = Charsets.UTF_8): ByteArrayInputStream = ByteArrayInputStream(toByteArray(charset))
Therefore, we can make use of this extension function to perform the same conversion in a more readable way:
val theString = "Kotlin is awesome!"
val inputStream = theString.byteInputStream()
assertThat(inputStream).hasContent(theString)
5. 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.
Further, we’ve quickly looked at String to InputStream conversion.
As usual, the implementation of all these examples is over on GitHub.