JAVA / FILES, INPUT/OUTPUT AND SERIALIZATION
Scanner pitfalls with newlines, delimiters and validation
Mix token reads with nextLine() safely, set delimiters that do not swallow newlines, and validate input with hasNextX before reading it.
What you will learn
- Call nextLine() once after nextInt() to drop the rest of the number's line
- Write custom delimiters as a regex that also matches line breaks
- Guard every next() with the matching hasNext() instead of catching exceptions
- Discard a rejected token with next(); hasNextInt() peeks and never advances
Understanding Scanner pitfalls with newlines, delimiters and validation
A Scanner is a cursor over a character source plus a delimiter pattern, and its two families of methods move that cursor differently. next(), nextInt() and their relatives skip leading delimiters, match exactly one token, and then stop on the first character after the token, which means the line break that ended the line is still unread. nextLine() ignores the delimiter pattern completely: it consumes everything up to and including the next line terminator and returns the part before it. Put those two facts together and the classic bug explains itself, because after nextInt() the cursor sits just before the line break, so nextLine() has an empty rest-of-line to hand back.
The default delimiter is a whitespace pattern, which is why spaces, tabs and newlines all look identical to the token methods; with defaults in place a Scanner has no concept of lines at all. useDelimiter takes a regex and replaces that pattern outright instead of adding to it, so after useDelimiter(",") a newline is ordinary text and gets glued onto the last token of every line. Saying it explicitly, as in useDelimiter(",|\R"), makes both a comma and any line break end a token. The argument is a regex, so a delimiter of "." or "|" will not mean the literal character you had in mind.
Validation works because hasNextInt() looks ahead without moving the cursor: it applies the integer pattern to the next token and reports yes or no. The flip side is that a failed nextInt() throws InputMismatchException with the offending token still in place, so a retry loop that never calls next() to throw that token away spins forever. The same asymmetry guards the end of input: next() and nextLine() throw NoSuchElementException when nothing is left, and hasNext()/hasNextLine() are the cheap way to ask first.
import java.util.Scanner;
public class ScannerNewlines {
public static void main(String[] args) {
String input = "42\nAda Lovelace\n";
Scanner broken = new Scanner(input);
int ageA = broken.nextInt();
String nameA = broken.nextLine(); // rest of the number's line: nothing
System.out.println("broken: age=" + ageA + " name=[" + nameA + "]");
Scanner fixed = new Scanner(input);
int ageB = fixed.nextInt();
fixed.nextLine(); // discard the leftover line break
String nameB = fixed.nextLine();
System.out.println("fixed: age=" + ageB + " name=[" + nameB + "]");
}
}Token methods stop before the line terminator while nextLine() consumes it, so every empty-or-missing-input Scanner bug is really a question of where the cursor is.
Worked examples
A delimiter that forgets newlines
Shows how useDelimiter replaces the whitespace pattern and leaves the line break inside the last token.
import java.util.Scanner;
public class Delimiters {
public static void main(String[] args) {
String line = "red,green,blue\n";
Scanner loose = new Scanner(line).useDelimiter(",");
while (loose.hasNext()) {
System.out.println("loose [" + loose.next() + "]");
}
Scanner tight = new Scanner(line).useDelimiter(",|\\R");
while (tight.hasNext()) {
System.out.println("tight [" + tight.next() + "]");
}
}
}Example explained
Line 1useDelimiter(",") throws away the default whitespace pattern, so the newline stops being a separator.
Line 2The third loose token is the characters blue followed by the newline, which is why its closing bracket lands on the next line.
Line 3\R in the regex matches any line break (Java 8 and later), so ",|\R" ends a token at a comma or at the end of the line.
Line 4Both loops end after blue because hasNext() is false once only delimiters, or nothing at all, remain.
Look before you read
Uses hasNextInt() to filter tokens and shows why the rejected token must be consumed by hand.
import java.util.Scanner;
public class Validate {
public static void main(String[] args) {
Scanner in = new Scanner("12 abc 7");
int sum = 0;
while (in.hasNext()) {
if (in.hasNextInt()) {
sum += in.nextInt();
} else {
System.out.println("skipped [" + in.next() + "]");
}
}
System.out.println("sum=" + sum);
}
}Example explained
Line 1hasNextInt() tests the next token against the integer pattern without moving the cursor, so it can be called safely any number of times.
Line 2The else branch calls next() to consume abc; drop that call and hasNextInt() keeps reporting false on the same token forever.
Line 3No InputMismatchException can happen here, because nextInt() is only reached after the look-ahead already said the token parses.
Line 4sum=19 is 12 + 7, proving abc was skipped rather than silently treated as zero.
One line per record
Reads whole lines and parses them separately, which sidesteps cursor questions entirely.
import java.util.Scanner;
public class LineAtATime {
public static void main(String[] args) {
Scanner in = new Scanner("7\n \n-3\n");
while (in.hasNextLine()) {
String line = in.nextLine().trim();
if (line.isEmpty()) {
System.out.println("blank line skipped");
continue;
}
System.out.println("doubled " + (Integer.parseInt(line) * 2));
}
}
}Example explained
Line 1nextLine() returns the line without its terminator, so trim() only has to deal with the space that forms the middle line.
Line 2hasNextLine() is false once the cursor is past the final newline, so the trailing line break does not produce a phantom empty record.
Line 3continue skips the record without touching the Scanner, which is safe precisely because nextLine() already advanced past that line.
Line 4Parsing with Integer.parseInt keeps failures local: a bad line cannot leave an unread token behind to derail the next iteration.
Important notes
nextLine() does not use the delimiter pattern, so changing the delimiter never changes what nextLine() returns; it always searches for line terminators.
Number methods follow the Scanner's locale, so hasNextDouble() can be false for 3.5 when the default locale uses a comma as the decimal separator; call useLocale(Locale.ROOT) for machine-generated data.
Common mistakes
Reading an age with nextInt() and then a name with nextLine(): the name comes back empty and every later read is shifted by one record, so the damage usually shows up as a NumberFormatException on a line of text.
Switching to useDelimiter(",") for comma-separated input and then comparing or parsing the last field of each line: it still carries the newline, so equals() fails and Integer.parseInt throws NumberFormatException.
Catching InputMismatchException in a retry loop without calling next(): the failed nextInt() never consumed the bad token, so the prompt reprints in a tight infinite loop.
Try it yourself
Change, predict, then run
Starting from the string "3\nAda,Grace,Alan\n", read the leading 3 with nextInt() and then print each name on its own line inside square brackets. Change the delimiter so that both the comma and the line break end a token, and check that the bracket after Alan is not pushed onto a new line.
Open the Java workspaceCheck your understanding
A program scans the input 10\nhello, calls nextInt(), then calls nextLine(), and gets back an empty string. What does that tell you about the cursor?
- nextInt() consumed the whole first line including its newline, so nextLine() returned the empty text sitting before hello
- nextLine() always returns an empty string on its first call and has to be called a second time
- nextInt() stopped right after the digits, so the only thing left on that line was the newline, which nextLine() consumed and reported as empty
- The default whitespace delimiter matched the newline and turned it into an empty token that nextLine() returned
Show answer
Token methods match one token and stop immediately after it, leaving the line terminator unread; nextLine() then finds nothing before that terminator and returns an empty string while consuming it. Option 0 is the tempting one but contradicts itself: if nextInt() had already eaten the newline, nextLine() would have returned hello. Option 3 confuses the two method families, since nextLine() never looks at the delimiter pattern and delimiters are skipped rather than returned as tokens.