Java Character Streams Overview
Fast recall
Quick revision
Core answer
Reader consumes characters and Writer produces them; byte-to-text bridges apply a charset, while buffering and formatting wrappers add higher-level operations.Interview-ready guide
Interview answer
Character streams are Java's abstraction for reading and writing text. Reader is the base class for character input, and Writer is the base class for character output. Their API works with Java char values and strings, so it is different from InputStream and OutputStream, which move raw bytes.
Text still becomes bytes when it is stored in a file or sent over a network. InputStreamReader is the bridge that decodes bytes into characters with a Charset; OutputStreamWriter performs the reverse encoding. FileReader and FileWriter are file-oriented conveniences. Constructors without a charset use the default charset, so portable code normally passes StandardCharsets.UTF_8 or uses Files.newBufferedReader and Files.newBufferedWriter with an explicit charset.
Wrappers add capabilities. BufferedReader groups reads and provides readLine, while BufferedWriter groups writes and provides newLine. StringReader and StringWriter work entirely in memory. PrintWriter supplies print, println, and printf, but its output methods record I/O failures instead of throwing them directly; checkError() must be used when failure matters.
For example, a service can open a UTF-8 configuration file with Files.newBufferedReader, read each line, and let try-with-resources close the reader. The charset handles decoding, the buffer avoids many tiny file operations, and the application handles the meaning of each line.
Character streams are for text, not arbitrary binary content. They also operate on UTF-16 code units, so one visible Unicode character can require two char values. The practical rule is to choose a Reader or Writer for text, state the charset at byte boundaries, add buffering when useful, and close every owned external resource.
Java Character Streams Overview
Direct Answer
Character streams are Java's text I/O abstractions. Reader consumes characters and Writer produces them; byte-to-text bridges apply a charset, while buffering and formatting wrappers add higher-level operations.
Deep Dive & Architecture
A file does not contain Java characters; it contains bytes. A charset defines how byte sequences represent text. When an InputStreamReader reads UTF-8, its decoder may need several input bytes before it can produce one character. On output, an OutputStreamWriter performs the inverse operation and may retain bytes internally until it is flushed or closed.
The Reader API reports UTF-16 code units. Most everyday text can be processed as strings, but code that counts user-perceived characters should not assume that one char equals one Unicode code point or one grapheme. Supplementary code points occupy a surrogate pair. This is a property of Java's text model, not a decoding failure.
Buffering is a separate concern from decoding. An InputStreamReader knows how bytes map to characters; a BufferedReader placed above it reduces calls to the underlying reader and adds line-oriented operations. Closing the outer reader closes the wrapped reader, which in turn closes its underlying byte stream. The same layering applies on output.
Encoding errors need an explicit policy in strict systems. Convenience readers usually use the decoder's replacement behavior for malformed input. When an application must reject malformed data, it can configure a CharsetDecoder with CodingErrorAction.REPORT and pass it to an InputStreamReader. This keeps decoding policy near the byte-to-text boundary instead of scattering assumptions through business logic.
Code Example
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
class CharacterStreamOverview {
public static void main(String[] args) throws Exception {
byte[] bytes = "naïve café".getBytes(StandardCharsets.UTF_8);
try (var reader = new BufferedReader(new InputStreamReader(
new ByteArrayInputStream(bytes), StandardCharsets.UTF_8))) {
String text = reader.readLine();
if (!"naïve café".equals(text)) throw new AssertionError(text);
System.out.println(text);
}
}
}
Check your understanding
Answer these without looking back at the article.
- How is InputStreamReader different from BufferedReader?
- Why should a charset be explicit at a byte boundary?
- Does one Java char always represent one visible character?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.