IO & NIO

Every real program reads and writes data — config files, logs, uploads, exports. By the end of this lesson you'll read and write files the clean, modern way, stream huge files without running out of memory, and know exactly when to drop down to channels and buffers.

Learn IO & NIO in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

You should already know Exception Handling (especially try-with-resources) and the Streams API , since the modern file methods return a Stream . A basic feel for the difference between bytes (raw data) and characters (text) helps too.

Keep that picture in mind: streams = the straw, buffers + channels = the brigade, and the Files class = the button that hides the brigade behind a one-liner.

A stream is a one-way flow of data. Java splits them in two: InputStream / OutputStream move raw bytes (images, zip files), while Reader / Writer move characters (text). For files you'd use FileReader / FileWriter .

Reading one character at a time is slow, so you wrap a reader in a BufferedReader . "Buffered" means it grabs a big chunk into memory and hands it to you piece by piece — and it adds the handy readLine() method. The try (...) block below is try-with-resources : anything you open in those parentheses is closed automatically when the block ends.

Read the fully-commented example, then run it. Note how readLine() returns null at the end of the stream — that's your signal to stop the loop.

Since Java 7, java.nio.file replaced most of that ceremony. A Path is simply a location on disk (the modern stand-in for the legacy File class). The Files class is a toolbox of static helpers that turn whole tasks into one line.

Files.readString(path) — read an entire small file into a String (Java 11+)

Files.readAllLines(path) — read every line into a List String

Files.writeString(path, s) — write a String, creating or overwriting the file

Files.lines(path) — a lazy Stream String , safe for huge files

The example below writes a file, reads it three different ways, then deletes it. The key habit: always pass StandardCharsets.UTF_8 so the same code reads the same text on every machine.

Finish the program: write three lines, read them back as a List , and print the count. Replace each ___ using the // 👆 hints. Check your work against the expected output in the comments.

Count the WARN lines in a log without loading the whole file into memory. Fill in the streaming method and the text to match, then compare with the expected output.

The convenient Files helpers are built on lower-level pieces you'll occasionally meet. A channel ( FileChannel ) is a two-way pipe to a file or socket. A buffer ( ByteBuffer ) is a fixed-size box of bytes that the channel fills from or drains into.

The one trick people forget is buffer.flip() : after you fill a buffer you must flip it to switch from "write mode" to "read mode" so the channel knows where your data ends. You rarely need this layer directly — but it's what powers fast, large-file work like FileChannel.transferTo() (zero-copy) and memory-mapped files.

No blanks this time — just an outline. Write a sentence to a temp file, read it back with Files.readString , split it on spaces, and print the word count. The starter has only comments; you write the code.

Great work! You can now read and write files the classic way with buffered Reader / Writer , close them safely with try-with-resources, and do the same far more cleanly with the modern Path + Files API. You also know to stream huge files with Files.lines instead of loading them whole, and what channels, buffers, and non-blocking IO are for.

Next up: JDBC — connecting your Java programs to databases so your data outlives a single run.

Practice quiz

What does BufferedReader.readLine() return at the end of the stream?

  • an empty string
  • -1
  • null
  • it throws EOFException

Answer: null. readLine() returns null when there are no more lines, which is your signal to stop the loop. Verified on Java 21.

What is the difference between Reader/Writer and InputStream/OutputStream?

  • Reader/Writer move characters (text); streams move raw bytes
  • Reader/Writer move bytes; streams move characters
  • They are identical
  • Only Reader/Writer can be buffered

Answer: Reader/Writer move characters (text); streams move raw bytes. Reader/Writer handle character (text) data, while InputStream/OutputStream move raw bytes like images or zip files.

Do you still need to manually close a stream declared in a try-with-resources header?

  • Yes, in a finally block
  • Only if an exception is thrown
  • Only for Writers
  • No — it is closed automatically when the block ends, even on exception

Answer: No — it is closed automatically when the block ends, even on exception. Any AutoCloseable declared in the try (...) parentheses is closed automatically at block exit, even when an exception is thrown.

Which method reads an entire small file into a single String (Java 11+)?

  • Files.lines
  • Files.readString
  • Files.walk
  • Files.copy

Answer: Files.readString. Files.readString(path) reads a whole small file into one String; for large files use Files.lines instead.

Why prefer Files.lines over Files.readAllLines for a multi-gigabyte log?

  • Files.lines returns a lazy Stream holding one line at a time, avoiding OutOfMemoryError
  • readAllLines is deprecated
  • Files.lines is faster to type
  • readAllLines cannot read logs

Answer: Files.lines returns a lazy Stream holding one line at a time, avoiding OutOfMemoryError. Files.readAllLines loads every line into a List (risking OOM); Files.lines streams lazily, keeping only one line in memory at a time.

Why should you always pass StandardCharsets.UTF_8 to file methods?

  • It makes files smaller
  • It is required to compile
  • Without it, older APIs use the platform default, so text can differ or corrupt across machines
  • UTF_8 is the only charset Java supports

Answer: Without it, older APIs use the platform default, so text can differ or corrupt across machines. Omitting a charset falls back to the platform default, which varies by OS and can corrupt text; UTF_8 makes behaviour identical everywhere.

What does buffer.flip() do on a ByteBuffer?

  • Reverses the byte order
  • Sets the limit to the current position and resets position to 0, switching write mode to read mode
  • Empties the buffer
  • Doubles the buffer capacity

Answer: Sets the limit to the current position and resets position to 0, switching write mode to read mode. flip() prepares a just-filled buffer to be read by setting limit to the current position and position to 0; forgetting it is the classic NIO bug.

Counting ERROR lines in 'ERROR a / INFO b / ERROR c / DEBUG d / ERROR e' with Files.lines(...).filter(l -> l.contains("ERROR")).count() gives what?

  • 2
  • 5
  • 1
  • 3

Answer: 3. Three of the five lines contain ERROR, so the count is 3. Verified by running it on Java 21.

What is a FileChannel?

  • A character stream for text
  • A two-way pipe to a file or socket that reads into / writes from a buffer
  • A replacement for Path
  • A logging utility

Answer: A two-way pipe to a file or socket that reads into / writes from a buffer. A channel (like FileChannel) is a two-way pipe; a ByteBuffer is the fixed-size box of bytes it fills from or drains into.

What is the modern NIO.2 replacement for the legacy java.io.File class?

  • Stream
  • Reader
  • Path
  • Channel

Answer: Path. A Path represents a location on disk and is the modern stand-in for File, with the Files class providing static helper operations.