The next* Cascade and the nextLine Trap
next, nextLine, nextInt, nextDouble — and why the order of hasNext checks matters
In a nutshell
Scanner has a small family of read methods, and the difference between them is more important than students usually expect. Three rules to memorize today:
next(),nextInt(),nextDouble()skip whitespace and read one token. They do not consume the trailing newline. The cursor parks just after the token, with the newline still ahead.nextLine()reads the rest of the current line, including consuming the newline at the end. It returns whatever is on the line up to the\n(the\nitself is not in the returned string).hasNextInt()must be checked beforehasNextDouble(). Everyintliteral also satisfieshasNextDouble(), so checking double first will swallow integers as doubles. Order: int → double → general token.
The third rule is the order of the cascade. The first two rules together cause the leftover-newline trap: a nextInt() followed by a nextLine() makes the nextLine() return an empty string, because the newline left behind by nextInt() is consumed by the very next nextLine(). The fix is one throwaway sc.nextLine() between them.
Today in three sentences. Scanner has a cursor that walks left to right.
nextInt/nextDouble/nextconsume a token but leave the surrounding whitespace (including newlines) alone.nextLineconsumes everything up to and including the next newline, which is why it interacts withnextIntin surprising ways.
After this lesson, you will be able to:
- Choose between
next(),nextLine(),nextInt(), andnextDouble()for a given input shape. - Apply the
hasNextInt→hasNextDouble→hasNextcascade in the correct order. - Recognize the leftover-newline trap on sight and fix it with a throwaway
nextLine(). - Read a mixed-type file (header + data) with the right combination of readers.
From CSCD 110. Python’s
for line in f:andline.split()give you the line-and-token split for free, but you decide once which one you want. Java’s Scanner gives you finer control — you mix lines and tokens within the same loop — but the price is keeping the cursor model in your head. Learn the cursor model and the trap goes away.
Four readers, four jobs
The Scanner you have is the same Scanner you used on the keyboard. The four read methods you’ll use most often:
| Method | What it consumes | What it returns | Skips leading whitespace? | Consumes trailing newline? |
|---|---|---|---|---|
next() |
one whitespace-delimited token | String (the token, no whitespace) |
yes | no |
nextInt() |
one token, parsed as int |
int |
yes | no |
nextDouble() |
one token, parsed as double |
double |
yes | no |
nextLine() |
everything up to and including the next \n |
String (line content, without the trailing \n) |
no | yes |
The pattern that hides under that table: the three “token” methods leave the cursor alone with respect to whitespace; nextLine consumes a newline. That difference is responsible for the most painful bug in this lesson.
A cursor model
Think of the Scanner as a cursor that walks left-to-right through the file’s bytes. Whitespace (spaces, tabs, newlines) shows up as physical characters between tokens. After every read, the cursor is parked at a specific spot.
Suppose the file data.txt contains these three lines (with ↵ representing the newline):
42 17↵
hello world↵
3.14↵
After int n = sc.nextInt();:
- The cursor consumed the
42. - The cursor is now sitting just after
42, before the space.
After a second int m = sc.nextInt();:
- The cursor skipped the leading space (whitespace before a token is consumed).
- It read
17. - It’s now sitting just after
17, before the↵. The newline has not been consumed.
Now if you call String s = sc.nextLine();:
nextLinedoes not skip leading whitespace. It reads from the cursor position to the next newline.- The cursor was sitting just before
↵, sonextLine()returns the empty string""and consumes the↵.
That’s the leftover-newline trap, in slow motion. We’ll come back to it after the cascade rule.
Common pitfall: thinking
nextInt()consumes the newline. It does not. It consumes the integer token (and skips leading whitespace), but it parks the cursor just after the digits. The newline is still there, waiting to bite the nextnextLine()call.
Check your understanding. Given the file
data.txtfrom above, what does this code print?Scanner sc = new Scanner(new File("data.txt")); int a = sc.nextInt(); int b = sc.nextInt(); String word1 = sc.next(); String word2 = sc.next(); double pi = sc.nextDouble(); System.out.println(a + " " + b + " " + word1 + "/" + word2 + " " + pi);Reveal answer
Prints
42 17 hello/world 3.14. The twonextInt()calls read42and17. The twonext()calls each read one token, skipping the newlines and spaces between them.nextDouble()reads3.14. The token methods all skip whatever whitespace they need to find the next token, including newlines. They just don’t consume trailing whitespace after the token.
hasNextInt before hasNextDouble: the order rule
The cascade pattern lets you read mixed-type input safely:
while (sc.hasNext()) {
if (sc.hasNextInt()) {
int n = sc.nextInt();
System.out.println("int: " + n);
} else if (sc.hasNextDouble()) {
double d = sc.nextDouble();
System.out.println("double: " + d);
} else {
String s = sc.next();
System.out.println("word: " + s);
}
}
That program reads a file token by token and labels each token as int, double, or word. The order of the checks matters more than it first looks like.
Rule: check hasNextInt() before hasNextDouble(). Here’s why.
hasNextDouble() returns true for any token that parses as a double. Every integer literal — 42, -17, 0 — also parses as a double (42.0, -17.0, 0.0). So if you write the cascade in the wrong order:
if (sc.hasNextDouble()) { // BUG: catches every int too
double d = sc.nextDouble();
System.out.println("double: " + d);
} else if (sc.hasNextInt()) {
// unreachable: every int passes hasNextDouble first
int n = sc.nextInt();
System.out.println("int: " + n);
}
…the int branch never fires. Every integer in the file is reported as a double (42.0 instead of 42).
The general principle: when checking with hasNext*, go from most specific to most general. hasNextInt is a strict subset of hasNextDouble, which is a strict subset of hasNext. Check the strictest first.
Common pitfall: putting
hasNextDouble()first because doubles “feel more general.” They are more general — that’s exactly why this order is wrong. The more general check matches more inputs, including ones the next branch was supposed to handle. Strictest first.
Check your understanding. A file
mixed.txtcontains:42 3.14 hello -7 9.99 worldUsing the correctly-ordered cascade above (int → double → word), what does the program print, line by line?
Reveal answer
int: 42 double: 3.14 word: hello int: -7 double: 9.99 word: worldEach token is checked in order: is it an int? is it a double? otherwise it’s a word.
42and-7matchhasNextIntfirst (correctly classified as int).3.14and9.99failhasNextIntbut matchhasNextDouble.helloandworldfail both numeric checks and fall through tonext().
Check your understanding. A file
numbers.txtcontains exactly two tokens:42and3.14. The cascade is written in the wrong order (double first). What does the program print?while (sc.hasNext()) { if (sc.hasNextDouble()) { double d = sc.nextDouble(); System.out.println("double: " + d); } else if (sc.hasNextInt()) { int n = sc.nextInt(); System.out.println("int: " + n); } else { System.out.println("word: " + sc.next()); } }Reveal answer
double: 42.0 double: 3.14The token
42matcheshasNextDouble()first (because every int is also a valid double), so the double branch fires and prints42.0. The int branch is dead code. Same fix as the rule says: puthasNextInt()first.
The leftover-newline trap
This is the bug that catches more students every term than any other Scanner bug. It looks like this:
Scanner sc = new Scanner(System.in);
System.out.print("Enter your age: ");
int age = sc.nextInt();
System.out.print("Enter your name: ");
String name = sc.nextLine();
System.out.println("Hello " + name + ", you are " + age);
Run it. Type 21, hit Enter, then type Alice, hit Enter. The output:
Enter your age: 21
Enter your name: Hello , you are 21
The program never asked for a name, and name is the empty string. Why?
Walk the cursor through the input. After you typed 21<Enter>, the buffer holds 21↵. The cursor is at the start.
sc.nextInt()reads the21, parks the cursor just after the digits, before the↵. The↵is still in the buffer.sc.nextLine()reads from the cursor position to the next newline. The cursor was sitting on the↵, so it reads zero characters, returns"", and consumes the↵.- The program prints
Hello , you are 21and exits — never reaching the point where it could readAlice.
The exact same trap fires when reading from a file. Here’s the file version. report.txt:
3
red
blue
green
The intent: read the count 3, then read three color names. The buggy code:
Scanner sc = new Scanner(new File("report.txt"));
int n = sc.nextInt();
for (int i = 0; i < n; i++) {
String color = sc.nextLine(); // BUG
System.out.println("color " + i + ": [" + color + "]");
}
Output:
color 0: []
color 1: [red]
color 2: [blue]
The first nextLine() consumed the leftover ↵ after 3, returning "". Now i = 1 reads red, i = 2 reads blue, and green is never reached.
The fix: throwaway nextLine()
After every nextInt() or nextDouble() that is followed by a nextLine() you actually care about, insert a throwaway nextLine() to consume the leftover newline:
Scanner sc = new Scanner(new File("report.txt"));
int n = sc.nextInt();
sc.nextLine(); // <-- throwaway: eats the leftover ↵
for (int i = 0; i < n; i++) {
String color = sc.nextLine();
System.out.println("color " + i + ": [" + color + "]");
}
Now the output is:
color 0: [red]
color 1: [blue]
color 2: [green]
The throwaway nextLine() consumed the ↵ left over by nextInt(). The first real nextLine() inside the loop now reads red as intended.
Mental model: a token method and a line method don’t speak the same dialect. Token methods (
next,nextInt,nextDouble) treat whitespace as a separator and ignore it after the token. The line method (nextLine) treats the newline as a boundary and consumes it. When you mix them, you have to translate at the boundary — that translation is the throwawaynextLine().
Common pitfall: applying the throwaway in the wrong place. Put it between the last
nextInt/nextDoubleand the nextnextLine. If you put it after thenextLine, you eat real data. If you put it before thenextInt, you don’t fix anything.
Check your understanding. A file
inventory.txtcontains:3 apples oranges bananasWrite a small program (filling in the missing line) that reads
nand then prints the nextnlines.Scanner sc = new Scanner(new File("inventory.txt")); int n = sc.nextInt(); // <-- one line goes here for (int i = 0; i < n; i++) { System.out.println(sc.nextLine()); }Reveal answer
Insert
sc.nextLine();(no assignment, no parameter — just call and discard). The program now printsapples,oranges,bananas. Without it, the firstnextLine()inside the loop returns the empty string left behind bynextInt(), andbananasis never read because the loop ran out of iterations.
Putting it together: a mixed-token file
A realistic file: a header line with a count, then one record per line where each record is a name and a score separated by a space.
scores.txt:
4
Ada 95
Linus 82
Margaret 100
Donald 70
Read it with the cascade pattern, plus the throwaway-newline fix:
public static void main(String[] args) throws FileNotFoundException {
Scanner sc = new Scanner(new File("scores.txt"));
int n = sc.nextInt();
sc.nextLine(); // throwaway: eat the ↵ after the count
for (int i = 0; i < n; i++) {
String name = sc.next(); // first token on the line
int score = sc.nextInt(); // second token
sc.nextLine(); // throwaway: eat the ↵ after the score
System.out.println(name + ": " + score);
}
sc.close();
}
Output:
Ada: 95
Linus: 82
Margaret: 100
Donald: 70
Notice the second throwaway inside the loop. Each record line has the same nextInt → leftover-↵ shape, so each iteration ends with a throwaway. If the next thing you wanted to read on the next iteration was a next() (a token), you wouldn’t need the throwaway — token methods skip the leftover whitespace for free. It’s only when the next read is a nextLine() that the throwaway is required.
Check your understanding. Could you replace the inner
sc.next(); sc.nextInt(); sc.nextLine();block with threenextLine()calls and aString.split(" ")? When would each approach be better?Reveal answer
Yes, you can. The alternative looks like:
String line = sc.nextLine(); String[] parts = line.split(" "); String name = parts[0]; int score = Integer.parseInt(parts[1]);Use
nextLine + splitwhen each record is a single line that you want to parse as a unit (CSV-style data, fixed-format records). Use thenext + nextInt + throwaway-nextLinepattern when records can span multiple lines or when whitespace inside a record is irregular. Both work. Pick the one whose shape matches the file’s shape.
Wrap up and what’s next
Recap.
- Four readers:
next(),nextInt(),nextDouble()(token-based, skip leading whitespace, leave trailing newline alone) andnextLine()(line-based, consumes through the next\n). - Cascade order:
hasNextInt()→hasNextDouble()→hasNext(). Strictest first. Reversing the first two swallows ints as doubles. - Leftover-newline trap: a
nextInt()(ornextDouble()) followed by anextLine()makes thenextLine()return"". Fix: throwawaysc.nextLine();between them. - Two throwaway placements that both work in their own situations: alternative to the throwaway is
nextLine+splitfor record-per-line files.
What you can do now. Read a mixed-type file confidently. Recognize the leftover-newline trap on sight and apply the throwaway. Order the cascade correctly without thinking about it.
Next up: Writing Files with PrintStream. The output side of the file-I/O picture. System.out is a PrintStream, so the API is already familiar — the only new piece is pointing one at a file. Closing matters; we walk through what happens to your output if you forget.
Related resources
- Reges & Stepp, Building Java Programs, Chapter 6 sections 6.2 and 6.3 cover the next* methods, the hasNext* family, and the leftover-newline trap with the same cursor model.
- The
ScannerJavadoc — thenext*andhasNext*method docs spell out the consume-vs-skip semantics in formal language. Worth reading once.