Question
Given an InputStream in Kotlin, what is the idiomatic, concise, and safe way to read its entire contents into a String?
The solution should:
- close the stream reliably,
- use an explicit character encoding,
- avoid unnecessarily verbose line-by-line loops, and
- preserve the original text, including line separators, when appropriate.
For example, instead of manually copying bytes or reading and appending each line, how should this be written idiomatically?
Short Answer
You will learn to convert a Kotlin InputStream to a String with bufferedReader(), readText(), and use. You will also learn why choosing a charset and avoiding line-by-line concatenation matters.
Concept
An InputStream supplies bytes, not text. To turn those bytes into a String, Kotlin and Java must decode them using a character set such as UTF-8.
The usual idiomatic Kotlin form is:
val text = input.bufferedReader(Charsets.UTF_8).use { reader ->
reader.readText()
}
This works because:
bufferedReader(Charsets.UTF_8)wraps the byte stream in aBufferedReaderthat decodes UTF-8 bytes into characters efficiently.readText()reads all remaining characters from the reader.use { ... }closes the reader when the block finishes, even if an exception occurs.- Closing the reader also closes the underlying
InputStream.
An explicit charset is important. Without one, code may use the machine's default charset, which can differ between computers, operating systems, containers, and production servers. UTF-8 is the common choice when a format or API specifies UTF-8.
Mental Model
Think of an InputStream as a pipe delivering numbered beads: bytes. A String is readable text.
A charset is the decoding legend that says how to interpret each bead or group of beads as letters. UTF-8 is one such legend. If you use the wrong legend, text such as café, emoji, or non-English characters can be decoded incorrectly.
use is like borrowing a book from a library with an automatic return box: after the block ends, Kotlin closes the resource for you, including when something goes wrong.
Syntax and Examples
Use bufferedReader with the expected charset, then call readText() inside use:
import java.io.InputStream
fun InputStream.readAllUtf8(): String =
bufferedReader(Charsets.UTF_8).use { reader ->
reader.readText()
}
Usage:
val input: InputStream = javaClass.getResourceAsStream("/message.txt")!!
val message = input.readAllUtf8()
println(message)
For a one-off read, an extension function is not required:
val text = input.bufferedReader(Charsets.UTF_8).use { it.readText() }
readText() reads the complete remaining content. Unlike readLine(), it does not split text into lines and therefore preserves existing newline characters.
If you already have a Reader, do not create another reader. Call readText() directly:
val text = reader.use { it.readText() }
Step by Step Execution
Consider this input, encoded as UTF-8:
Hello
Kotlin
Code:
val text = input.bufferedReader(Charsets.UTF_8).use { reader ->
reader.readText()
}
Execution:
inputprovides raw bytes.bufferedReader(Charsets.UTF_8)creates aBufferedReaderthat converts those UTF-8 bytes into characters and buffers reads for efficiency.usestarts a block with that reader.reader.readText()reads every remaining character, producing the string"Hello\nKotlin\n"when the source ends with a newline.- The value returned by the block becomes
text. usecloses the reader.- Closing the reader closes
inputas well.
The resulting string retains the line breaks because no code removed or replaced them.
Real World Use Cases
This pattern is useful whenever a resource is known to contain a reasonably sized text document:
- Reading a JSON error response from an HTTP connection.
- Loading a text file packaged in application resources.
- Reading a SQL script, configuration file, template, or Markdown document.
- Reading a command-line process's text output.
- Parsing a small CSV document before passing its text to another parser.
For example, reading a UTF-8 JSON response body from a HttpURLConnection:
val body = connection.inputStream
.bufferedReader(Charsets.UTF_8)
.use { it.readText() }
Only read the entire stream at once when its possible size is bounded or reasonably small. Large files and unbounded network responses should usually be processed incrementally.
Real Codebase Usage
In real projects, developers often wrap this operation in a small extension function when the same encoding convention is used repeatedly:
import java.io.InputStream
fun InputStream.readUtf8Text(): String =
bufferedReader(Charsets.UTF_8).use { it.readText() }
For an HTTP response, it is common to select the appropriate stream and then close it immediately after consuming it:
val stream = if (connection.responseCode in 200..299) {
connection.inputStream
} else {
connection.errorStream
}
val responseText = stream?.bufferedReader(Charsets.UTF_8)?.use { it.readText() }
For large data, process data as it arrives instead of building one giant string. For example, process lines only when line boundaries are meaningful:
input.bufferedReader(Charsets.UTF_8).useLines { lines ->
lines.filter { it.isNotBlank() }.forEach { line ->
println(line)
}
}
Use useLines for line-oriented processing, not as the default way to reconstruct the original text.
Common Mistakes
Forgetting to close the stream
This leaves a file handle, socket, or other resource open:
val text = input.bufferedReader(Charsets.UTF_8).readText()
Use use when your code owns the stream:
val text = input.bufferedReader(Charsets.UTF_8).use { it.readText() }
Relying on the platform default charset
val text = input.bufferedReader().use { it.readText() }
This may be acceptable only when the input's encoding is intentionally the platform default. Prefer the encoding defined by the file format, HTTP header, or API contract:
val text = input.bufferedReader(Charsets.UTF_8).use { it.readText() }
Reading lines and accidentally removing newlines
readLine() returns a line without its line separator. This code changes "one\ntwo\n" into "onetwo":
val text = input.bufferedReader().use { reader ->
buildString {
while (true) {
val line = reader.readLine() ?: break
append(line)
}
}
}
Use readText() when preserving the text matters. If you intentionally need normalized lines, join them explicitly:
val text = input.bufferedReader(Charsets.UTF_8).use { reader ->
reader.lineSequence().joinToString("\n")
}
This intentionally normalizes line separators and may not restore a final newline.
Using lineSequence() without closing the reader
val text = input.bufferedReader().lineSequence().joinToString("\n")
The sequence does not automatically close the reader. Put the full sequence consumption inside use.
Loading an unbounded or huge stream
readText() creates one complete String in memory. Do not use it for arbitrarily large uploads, logs, or responses. Stream and process chunks or lines instead.
Closing a stream you do not own
use closes the resource. If an API gives you an InputStream that another part of the program must continue using, do not close it. In that situation, document ownership clearly and read only the required portion.
Comparisons
| Approach | Best use | Closes resource? | Preserves original line separators? |
|---|---|---|---|
bufferedReader(UTF_8).use { it.readText() } | Read an entire text stream | Yes | Yes |
useLines { ... } | Process lines one at a time | Yes | No; separators are not included in lines |
forEachLine { ... } | Perform an action for each line | Yes | No; separators are not included in lines |
readBytes() then decode | Binary-first workflows | Only with surrounding use | Yes after correct decoding |
Manual readLine() loop | Custom line handling | Only if manually closed or wrapped in use | No unless you add separators yourself |
readText() is the clearest choice when the goal is exactly: “read all text from this stream.”
useLines and forEachLine are preferable when the file may be large and you can handle one line at a time.
A byte-based alternative can be useful when you first need bytes for another reason:
val text = input.use { it.readBytes().toString(Charsets.UTF_8) }
For ordinary text input, bufferedReader(...).use { it.readText() } communicates intent more directly.
Cheat Sheet
// Recommended: read all UTF-8 text and close the stream
val text = input.bufferedReader(Charsets.UTF_8).use { it.readText() }
// Reusable extension
fun InputStream.readUtf8Text(): String =
bufferedReader(Charsets.UTF_8).use { it.readText() }
// Process, rather than store, lines
input.bufferedReader(Charsets.UTF_8).useLines { lines ->
lines.forEach { line -> println(line) }
}
InputStreamcontains bytes;Stringcontains decoded characters.- Specify the charset required by the input format; UTF-8 is common.
usecloses resources even when an exception is thrown.- Closing a reader closes its wrapped
InputStream. readText()preserves line separators.readLine(),lineSequence(),useLines, andforEachLineexpose lines without their separators.- Avoid
readText()for unbounded or very large input.
FAQ
What is the shortest idiomatic way to read an InputStream as a String in Kotlin?
val text = input.bufferedReader(Charsets.UTF_8).use { it.readText() }
It decodes UTF-8 text and closes the stream.
Does use close the underlying InputStream?
Yes. When use closes the BufferedReader, the reader closes the InputStream it wraps.
Should I always use UTF-8?
Use the charset specified by the source. UTF-8 is appropriate when the source is defined as UTF-8, which is common for JSON, modern APIs, and many text files.
Does readText() preserve newlines?
Yes. It reads characters as they occur in the stream. Line-based APIs such as readLine() remove line separators.
Why not use ByteArrayOutputStream().toString()?
It can work, but it is more verbose for text. Also, toString() without a charset can depend on the platform default charset. A reader makes text decoding explicit.
Is useLines better than readText()?
Use useLines when you want to process lines incrementally. Use readText() when you need one complete string and want to preserve original line separators.
Can I call readText() on a large file?
You can, but it stores all text in memory. Process large or untrusted-size streams incrementally instead.
What happens if reading fails inside use?
The exception is propagated, and use still attempts to close the resource.
Mini Project
Description
Create a small loader that reads a UTF-8 application configuration from an InputStream. The loader should return the complete text, safely close the stream, and preserve the file's original line breaks.
Goal
Write a reusable Kotlin function that reads all UTF-8 text from an InputStream and demonstrate it with an in-memory configuration stream.
Requirements
Define an InputStream extension function named readUtf8Text.
Decode the stream using Charsets.UTF_8.
Close the reader and underlying stream automatically.
Return all remaining text as one String.
Demonstrate the function with a configuration string containing multiple lines.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.