file-io 18 min read

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:

  1. 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.
  2. 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 \n itself is not in the returned string).
  3. hasNextInt() must be checked before hasNextDouble(). Every int literal also satisfies hasNextDouble(), 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/next consume a token but leave the surrounding whitespace (including newlines) alone. nextLine consumes everything up to and including the next newline, which is why it interacts with nextInt in surprising ways.

After this lesson, you will be able to:

  • Choose between next(), nextLine(), nextInt(), and nextDouble() for a given input shape.
  • Apply the hasNextInthasNextDoublehasNext cascade 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: and line.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();:

  • nextLine does not skip leading whitespace. It reads from the cursor position to the next newline.
  • The cursor was sitting just before , so nextLine() 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 next nextLine() call.

Check your understanding. Given the file data.txt from 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 two nextInt() calls read 42 and 17. The two next() calls each read one token, skipping the newlines and spaces between them. nextDouble() reads 3.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, 0also 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.txt contains:

42 3.14 hello -7 9.99 world

Using 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: world

Each token is checked in order: is it an int? is it a double? otherwise it’s a word. 42 and -7 match hasNextInt first (correctly classified as int). 3.14 and 9.99 fail hasNextInt but match hasNextDouble. hello and world fail both numeric checks and fall through to next().

Check your understanding. A file numbers.txt contains exactly two tokens: 42 and 3.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.14

The token 42 matches hasNextDouble() first (because every int is also a valid double), so the double branch fires and prints 42.0. The int branch is dead code. Same fix as the rule says: put hasNextInt() 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.

  1. sc.nextInt() reads the 21, parks the cursor just after the digits, before the . The is still in the buffer.
  2. 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 .
  3. The program prints Hello , you are 21 and exits — never reaching the point where it could read Alice.

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 throwaway nextLine().

Common pitfall: applying the throwaway in the wrong place. Put it between the last nextInt/nextDouble and the next nextLine. If you put it after the nextLine, you eat real data. If you put it before the nextInt, you don’t fix anything.

Check your understanding. A file inventory.txt contains:

3
apples
oranges
bananas

Write a small program (filling in the missing line) that reads n and then prints the next n lines.

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 prints apples, oranges, bananas. Without it, the first nextLine() inside the loop returns the empty string left behind by nextInt(), and bananas is 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 three nextLine() calls and a String.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 + split when each record is a single line that you want to parse as a unit (CSV-style data, fixed-format records). Use the next + nextInt + throwaway-nextLine pattern 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) and nextLine() (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() (or nextDouble()) followed by a nextLine() makes the nextLine() return "". Fix: throwaway sc.nextLine(); between them.
  • Two throwaway placements that both work in their own situations: alternative to the throwaway is nextLine + split for 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.


  • 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 Scanner Javadoc — the next* and hasNext* method docs spell out the consume-vs-skip semantics in formal language. Worth reading once.