Reading input

The Scanner line that vanishes, and BufferedReader beating it twelve times on 300,000 integers.

4 min read Java Fundamentals

Reading input is where a first Java program meets the outside world, and it has one trap that catches nearly everybody and one performance cliff that matters the moment the input is large.

Scanner, and the line that vanishes

Scanner is the friendly one: it parses tokens for you.

java
Scanner s = new Scanner(System.in);
System.out.print("age? ");
int age = s.nextInt();
System.out.print("name? ");
String name = s.nextLine();

Given the input 30 then Ana:

plaintext
age? name? ### age  = [30]
### name = []
### did it even wait for the second answer?

The name is empty, the second prompt printed, and the program never waited. Ana was never read.

The cause is worth understanding rather than memorising a workaround for. nextInt() reads the digits 30 and stops there — it leaves the newline after them sitting in the buffer. nextLine() then reads "everything up to the next newline", finds one immediately, and returns the empty string between them.

So the rule: next, nextInt, nextDouble read tokens and leave the line ending; nextLine reads a line and consumes it. Mixing them is what breaks, and the two fixes are:

java
int age = s.nextInt();
s.nextLine();                          // consume the rest of that line
String name = s.nextLine();
 
// or: read lines only, and parse yourself
int age = Integer.parseInt(s.nextLine().trim());

The second is the one that scales, because it has one rule instead of two.

Two more edges worth knowing:

  • hasNextInt() before nextInt(). Otherwise non-numeric input throws InputMismatchException, and the offending token is not consumed — so a naive retry loop spins forever on the same bad token.
  • Locale. nextDouble uses the default locale, so 3.14 fails where the locale expects 3,14. For anything parsing data rather than talking to a person, new Scanner(in).useLocale(Locale.ROOT).

BufferedReader, and what "faster" actually means

The same 300,000 integers, summed two ways:

plaintext
### Scanner:          170 ms  (sum 44999850000)
### BufferedReader:    14 ms  (sum 44999850000)

Twelve times. Same answer, and the difference is not a micro-optimisation — it is the difference between a batch job finishing and a competitive-programming submission timing out.

The reason is what each one does per call:

  • Scanner is a tokeniser built on regular expressions. Every nextInt() matches a pattern against the stream, and it synchronises. That machinery is what makes it pleasant for a prompt and expensive in a loop.
  • BufferedReader.readLine() copies bytes into an array until it sees a line ending. No pattern, no parsing — you call Integer.parseInt yourself, which is a tight loop over digits.
java
try (BufferedReader r = new BufferedReader(new FileReader("big.txt"))) {
    String line;
    while ((line = r.readLine()) != null) {
        sum += Integer.parseInt(line);
    }
}

Note the shape of that loop: assign inside the condition, compare to null. It looks odd the first time and it is the idiom, because readLine() returning null is the only way to learn the stream ended.

Which to reach for

SituationUse
a prompt, a handful of valuesScanner — the convenience is worth it
a file, or thousands of linesBufferedReader
a whole small file at onceFiles.readAllLines(path)
a large file, line by line, modernFiles.lines(path) — a stream, and close it
production input from a requestneither; the framework has parsed it already

That last row matters more than it looks. In a Spring service you do not read System.in — the request body arrives parsed and validated, as the API course describes. Scanner and BufferedReader are for programs you run from a terminal: tools, imports, exercises, and the first hundred programs you write.

Closing, and the one you must not close

Both are resources. try-with-resources closes them:

java
try (BufferedReader r = new BufferedReader(new FileReader(path))) {
    ...
}

The exception is System.in. Closing a Scanner wrapped around it closes the underlying stream, and it cannot be reopened — every later read in that process fails. In a program that reads once at the start, wrapping System.in in a try-with-resources is a bug that only shows up in the second thing that reads.

Progress is saved on this device and to your account when signed in.