152

I recently saw code for reading entire contents of an InputStream into a String in Kotlin, such as:

// input is of type InputStream
val baos = ByteArrayOutputStream()
input.use { it.copyTo(baos) }
val inputAsString = baos.toString()

And also:

val reader = BufferedReader(InputStreamReader(input))
try {
    val results = StringBuilder()
    while (true) { 
        val line = reader.readLine()
        if (line == null) break
        results.append(line) 
    }
    val inputAsString = results.toString()
} finally {
    reader.close()
}

And even this that looks smoother since it auto-closes the InputStream:

val inputString = BufferedReader(InputStreamReader(input)).useLines { lines ->
    val results = StringBuilder()
    lines.forEach { results.append(it) }
    results.toString()
}

Or slight variation on that one:

val results = StringBuilder()
BufferedReader(InputStreamReader(input)).forEachLine { results.append(it) }
val resultsAsString = results.toString()   

Then this functional fold thingy:

val inputString = input.bufferedReader().useLines { lines ->
    lines.fold(StringBuilder()) { buff, line -> buff.append(line) }.toString()
}

Or a bad variation which doesn't close the InputStream:

val inputString = BufferedReader(InputStreamReader(input))
        .lineSequence()
        .fold(StringBuilder()) { buff, line -> buff.append(line) }
        .toString()

But they are all clunky and I keep finding newer and different versions of the same... and some of them never even close the InputStream. What is a non-clunky (idiomatic) way to read the InputStream?

Note: this question is intentionally written and answered by the author (Self-Answered Questions), so that the idiomatic answers to commonly asked Kotlin topics are present in SO.

Community
  • 1
  • 1
Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
  • I know its already perfectly answered but here is a perfectly described article from baeldung: https://www.baeldung.com/kotlin/inputstream-to-string :) – Tioman Mar 28 '22 at 10:36

4 Answers4

310

Kotlin has a specific extension just for this purpose.

The simplest:

val inputAsString = input.bufferedReader().use { it.readText() }  // defaults to UTF-8

And in this example, you could decide between bufferedReader() or just reader(). The call to the function Closeable.use() will automatically close the input at the end of the lambda's execution.

Further reading:

If you do this type of thing a lot, you could write this as an extension function:

fun InputStream.readTextAndClose(charset: Charset = Charsets.UTF_8): String {
    return this.bufferedReader(charset).use { it.readText() }
}

Which you could then call easily as:

val inputAsString = input.readTextAndClose()  // defaults to UTF-8

On a side note, all Kotlin extension functions that require knowing the charset already default to UTF-8, so if you require a different encoding you need to adjust the code above in calls to include encoding for reader(charset) or bufferedReader(charset).

Warning: You might see examples that are shorter:

val inputAsString = input.reader().readText() 

But these do not close the stream. Make sure you check the API documentation for all of the IO functions you use to be sure which ones close and which do not. Usually, if they include the word use (such as useLines() or use()) they close the stream after. An exception is that File.readText() differs from Reader.readText() in that the former does not leave anything open and the latter does indeed require an explicit close.

See also: Kotlin IO related extension functions

Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
  • 1
    I think "readText" would be a better name than "useText" for the extension function you propose. When I read "useText" I expect a function like `use` or `useLines` which executes a block function on what is being "used". e.g. `inputStream.useText { text -> ... }` On the other hand, when I read "readText" I expect a function which returns the text: `val inputAsString = inputStream.readText()`. – mfulton26 Sep 15 '16 at 13:53
  • True, but readText already has the wrong meaning, so wanted to signify it was more like the `use` functions in that regard. at least in context of this Q&A. maybe a new verb can be found... – Jayson Minard Sep 15 '16 at 21:10
  • 3
    @mfulton26 I went with `readTextAndClose()` for this example to avoid conflicting with `readText()` patterns of not closing, and with `use` patterns wanting a lambda, since I'm not trying to introduce a new stdlib function I don't want to do more than make a point about using extensions to save future labour. – Jayson Minard Sep 15 '16 at 23:05
  • @JaysonMinard why don't you mark this for answer? it's great though :-) – piotrek1543 Sep 17 '16 at 10:24
  • Since Kotlin 1.3, `readBytes` + String constructor is arguably simpler, but it is limited to 2GB so probably not worth recommending (hence this comment, rather than an answer): `inputStream.use { String(it.readBytes()) }`. Defaults to UTF-8, but you can also pass an encoding to the `String` constructor. Still, seems like using an `InputStreamReader` is a good habit if reading text, even when less than 2GB, which is probably most of the time. – Adam Millerchip Jul 22 '21 at 13:47
  • This reads the entire content into memory defeating the point of using a stream in the first place. – Jeffrey Blattman Sep 08 '22 at 18:52
  • @JeffreyBlattman it is what was requested to read it into a string, and there are valid reasons for that. – Jayson Minard Sep 18 '22 at 23:31
3

Method 1 | Manually Close Stream

private fun getFileText(uri: Uri):String {
    val inputStream = contentResolver.openInputStream(uri)!!

    val bytes = inputStream.readBytes()        //see below

    val text = String(bytes, StandardCharsets.UTF_8)    //specify charset

    inputStream.close()

    return text
}

Method 2 | Automatically Close Stream

private fun getFileText(uri: Uri): String {
    return contentResolver.openInputStream(uri)!!.bufferedReader().use {it.readText() }
}
Sam Chen
  • 7,597
  • 2
  • 40
  • 73
  • to improve the answer, please give some narrative explaining how this works – Kirby Sep 03 '20 at 23:54
  • Why do you mix `readBytes` and `readText` between your examples? Closing the stream automatically is handled by `use`, the choice of `BufferedReader`/`readText` and `readBytes` is not relevant to that. The second example is also the same as the example at the top of the accepted answer. – Adam Millerchip Jul 22 '21 at 13:25
2

An example that reads contents of an InputStream to a String

import java.io.File
import java.io.InputStream
import java.nio.charset.Charset

fun main(args: Array<String>) {
    val file = File("input"+File.separator+"contents.txt")
    var ins:InputStream = file.inputStream()
    var content = ins.readBytes().toString(Charset.defaultCharset())
    println(content)
}

For Reference - Kotlin Read File

arjun
  • 1,645
  • 1
  • 19
  • 19
  • 2
    Your example contains flaws: 1) For cross-platform paths you should use `Paths.get()` method. 2) For streams - try-resource feature (In kotlin: .use {}) – Eugene Lebedev Jul 04 '19 at 09:12
0

Quick solution works well when converting InputStream to string.

val convertedInputStream = String(inputStream.readAllBytes(), StandardCharsets.UTF_8)
dev_dan
  • 89
  • 6
  • There is no `readAllBytes` method on `InputStream`. Maybe you meant `readBytes`? If so, then you also need to close the stream. – Adam Millerchip Jul 22 '21 at 13:25