System.in is a raw InputStream. How do you read a line or tokens of text from it correctly, and what must you get right?
answer
- bytes -> charset -> chars -> parse
- Scanner vs BufferedReader+InputStreamReader
- readLine() returns null at EOF
- don't close System.in early
- set charset (UTF-8)
- setIn(ByteArrayInputStream) for tests
basics
~10 sSystem.in gives raw bytes, so wrap it. Use new Scanner(System.in) for tokens/lines, or new BufferedReader(new InputStreamReader(System.in)) and call readLine() for whole lines. Choose a charset and don't close System.in if you still need it.
solid answer
~40 sSystem.in is a byte InputStream, not text, so you wrap it to read characters. For simple token or line reading, new Scanner(System.in) offers nextInt(), nextLine(), etc. For efficient line reading, new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)) and readLine(). Three things to get right: (1) specify the charset on the InputStreamReader so non-ASCII input decodes correctly; (2) avoid closing the wrapper if you still need to read later, because closing it closes System.in too; and (3) handle end-of-input — readLine() returns null and Scanner's hasNext... returns false when the stream ends (e.g. piped input or Ctrl-D). For tests you can replace the source with System.setIn(new ByteArrayInputStream(bytes)).
code
java · 10 linesimport java.io.*;
import java.nio.charset.StandardCharsets;
var reader = new BufferedReader(
new InputStreamReader(System.in, StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null) { // null == end of input
System.out.println("echo: " + line);
}
// Note: we do NOT close 'reader' mid-program -- that would close System.in.go deeper
Wrap System.in with Scanner or BufferedReader+InputStreamReader and read a line; know it returns null/false at end of input.
Add the charset requirement, the close-closes-System.in pitfall, and the Scanner nextInt/nextLine newline gotcha.
Discuss blocking semantics, efficiency (BufferedReader vs Scanner), explicit charset for portability, and feeding input via setIn in tests.
Address robust CLI input handling: non-blocking/console interaction limits, encoding negotiation, and isolating I/O so the parsing logic is unit-testable without touching System.in.
## Why you can't read text directly from System.in `System.in` is an `InputStream`: its unit is the **byte**. Text is **characters**, and turning bytes into characters requires a **charset** (an encoding like UTF-8). So reading user input is always: bytes -> (charset) -> characters -> (parsing) -> values. You add wrappers for each step. ## The two common wrappers 1. **Scanner** — `new Scanner(System.in)`. High-level: `nextInt()`, `next()`, `nextLine()`, `hasNextLine()`. Convenient for small programs and parsing tokens, but slower and with a couple of gotchas (mixing `nextInt()` then `nextLine()` leaves a dangling newline). 2. **BufferedReader over InputStreamReader** — `new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))`. `InputStreamReader` does the byte->char decoding with the charset you give it; `BufferedReader` adds efficient `readLine()`. This is the standard, fast way to read lines. ## What you must get right - **Charset.** Always pass a charset to `InputStreamReader` (or use `Scanner(System.in, StandardCharsets.UTF_8)`); otherwise it uses the platform default and non-ASCII input may garble. - **End-of-input.** Input ends when the file/pipe is exhausted or the user signals EOF (Ctrl-D on Unix, Ctrl-Z on Windows). `BufferedReader.readLine()` returns `null` at EOF; `Scanner.hasNextLine()` returns `false`. Loop on these, don't assume input is infinite. - **Don't close prematurely.** Closing the `Scanner`/`BufferedReader` (or using try-with-resources) **closes `System.in`** underneath. Once closed you cannot read again for the rest of the process. Only close at the very end, and usually you simply don't close `System.in`. - **Blocking.** Reading from an interactive console **blocks** until the user types and presses Enter. That's expected for CLIs but means a thread is parked while waiting. ## Feeding input in tests Because `System.in` is just a field, swap it: `System.setIn(new ByteArrayInputStream("42\n".getBytes(StandardCharsets.UTF_8)))`. Then your code reads the canned bytes as if typed. Restore the original afterward, same as with `setOut`. ## First-principles takeaway Reading input = decode bytes with a charset, then parse. Pick a wrapper (Scanner for ease, BufferedReader for speed), set the charset, handle EOF, and don't close System.in until you're truly done.
- What does BufferedReader.readLine() return when the input ends, and how do you detect EOF with a Scanner?readLine() returns null at end-of-input. With a Scanner you check hasNextLine()/hasNext...() which return false at EOF.
- Why is closing your Scanner/BufferedReader on System.in often a mistake?Closing the wrapper closes the underlying System.in, so no further reads are possible for the rest of the process. Usually you leave System.in open and don't wrap it in try-with-resources.
saying these in an interview costs you the question
- Trying to read characters directly from System.in without an InputStreamReader/Scanner
- Omitting the charset and relying on the platform default
- Closing System.in (via try-with-resources on the wrapper) when more reads are needed
- Assuming readLine never returns null / input is infinite
- Confusing nextInt() leftover-newline behavior in Scanner