📁 I/O, Files & Networking · Intermediate

Byte streams vs character streams in Java

InputStream/OutputStream vs Reader/Writer.

🧩 The mysteryYou copy a photo with FileReader and FileWriter. The copy has the right size-ish… but won't open. The code looked fine. What did Java do to your pixels?

Two families

Java I/O comes in two families. **InputStream/OutputStream move raw bytes — right for images, ZIPs and network data. Reader/Writer move characters — right for text. Handy rule: names ending in Stream handle bytes; names ending in Reader/Writer** handle chars.

The bridge: bytes → chars

Text arrives as bytes, so something must decode them using a charset. **InputStreamReader wraps an InputStream and hands out characters; OutputStreamWriter** does the reverse.

Reader r = new InputStreamReader(
    System.in, StandardCharsets.UTF_8);
// System.in gives bytes, r gives chars
🔮 Predict it

Bytes in memory

ByteArrayOutputStream collects bytes in memory. What prints?

var out = new ByteArrayOutputStream();
out.write(65);
out.write(66);
out.write(67);
System.out.println(out.size());
System.out.println(
    out.toString(StandardCharsets.UTF_8));
  1. 3 ABC
  2. 6 656667
  3. 3 656667
Show the answer

3 — write(int) stores one byte each. Decoding those bytes as UTF-8 gives the characters **ABC** (65, 66, 67).

Why read() returns an int

read() returns the next byte (0–255) or character code as an **int, and -1 at the end** of the stream. Using int leaves room for -1, which can never be confused with real data.

🔮 Predict it

Reading to the end

What does this print?

void main() throws IOException {
    Reader r = new StringReader("hi");
    System.out.println(r.read());
    System.out.println(r.read());
    System.out.println(r.read());
}
  1. h i null
  2. 104 105 -1
  3. 104 105 0
Show the answer

104, 105 — the character codes of 'h' and 'i' — then **-1**: the end of the stream.

⚠️ The trap

Reading an image as text

A Reader decodes bytes into characters. Image bytes aren't valid text, so invalid sequences get replaced and the copy is corrupted. For binary data use **FileInputStream/FileOutputStream — or simply Files.copy**.

// corrupts binary files:
new FileReader("photo.png");
// correct:
Files.copy(src, dst);
💼 In the real world

Bytes vs text on the job

File uploads, PDFs, images and network protocols are bytes; CSVs, logs and JSON are text. Mixing them up causes corrupted downloads and mysterious "?" characters — a common bug when someone reads a binary file with a Reader "just to copy it".

Key takeaways

  1. InputStream/OutputStream: bytes (binary data)
  2. Reader/Writer: chars (text), decoded with a charset
  3. InputStreamReader turns bytes into chars
  4. read() returns an int; -1 means end of stream
🤯 Did you know?

System.out is a byte stream (PrintStream) under the hood: every println you've ever written encodes characters into bytes.

Practice questions

What does this print?

var out = new ByteArrayOutputStream();
out.write(72);
out.write(105);
System.out.println(out.size());
System.out.println(
    out.toString(StandardCharsets.UTF_8));
  1. 2 Hi
  2. 6 72105
  3. 2 72105
  4. 1 Hi
Check your answer

2 Hi. write(int) stores one byte each: 72 and 105. Decoding those 2 bytes as UTF-8 gives the characters 'H' and 'i'.

You need to copy a PNG image file. Which should you use?

  1. FileReader and FileWriter
  2. FileInputStream and FileOutputStream (or Files.copy)
  3. BufferedReader.readLine()
  4. Scanner
Check your answer

FileInputStream and FileOutputStream (or Files.copy). An image is binary. A Reader would try to decode the bytes as text, mangling any byte sequence that isn't valid in the charset.

Next: why reading a file one byte at a time is painfully slow — and the buffer that fixes it.