Command Palette

Search for a command to run...

[Java Basics] Reading Input in Java with Scanner (and the nextLine Trap)

Every program so far in this series has had its data baked into the source. This one takes data from outside: System.in for input, System.out and System.err for output. In Java the usual front door for reading System.in is java.util.Scanner.

Scanner is easy to start with and full of sharp edges. The most famous of them makes nextLine() return an empty string for no visible reason, and it costs beginners hours. All of it follows from one idea about how Scanner sees your input, so that idea is where this article starts. Every program and every error message below was compiled and run on OpenJDK 21.0.6.

Reading input in Java with Scanner

Output first, because you need it to see what your input code actually read.

Output: print, println and printf

System.out gives you three ways to write a line:

public class OutputDemo {
    public static void main(String[] args) {
        System.out.print("A");
        System.out.print("B");
        System.out.println("C");
        System.out.println("next line");
        System.out.printf("%s is %d years old%n", "An", 25);
        System.err.println("this went to stderr");
    }
}
ABC
next line
An is 25 years old
this went to stderr
  • print writes the text and leaves the cursor where it is. This is what you want for a prompt: System.out.print("Age: ") keeps the caret on the same line as the question.
  • println writes the text and ends the line.
  • printf writes a template with placeholders filled in from the arguments that follow.

The printf format specifiers you will actually use

A specifier is %, optional flags and width, then a conversion letter. Here is the set worth memorising, each one run on its own:

System.out.printf("%s%n", "An Nguyen");
System.out.printf("%d%n", 42);
System.out.printf("%f%n", 3.14159);
System.out.printf("%.2f%n", 3.14159);
System.out.printf("[%-10s]%n", "left");
System.out.printf("[%5d]%n", 42);
System.out.printf("%,d%n", 1234567);
System.out.printf("%b%n", true);
System.out.printf("100%%%n");
An Nguyen
42
3.141590
3.14
[left      ]
[   42]
1,234,567
true
100%
SpecifierMeaningOutput above
%sany value, via toString()An Nguyen
%dinteger types42
%ffloating point, 6 decimals by default3.141590
%.2ffloating point, 2 decimals, rounded3.14
%-10sstring padded to width 10, left alignedleft
%5dinteger padded to width 5, right aligned 42
%,dinteger with grouping separators1,234,567
%bbooleantrue
%%a literal percent sign%
%na line separatorends the line

The conversion letter must match the argument type. Passing a double to %d is not a rounding request, it is a crash:

System.out.printf("%d%n", 3.14);
Exception in thread "main" java.util.IllegalFormatConversionException: d != java.lang.Double
	at java.base/java.util.Formatter$FormatSpecifier.failConversion(Formatter.java:4515)

%n or \n?

\n is one character, the line feed, byte 10. %n is a printf directive that expands to the platform line separator: line feed on Linux and macOS, carriage return plus line feed on Windows. On this machine they produce identical bytes:

System.out.println("line.separator bytes: "
        + java.util.Arrays.toString(System.lineSeparator().getBytes()));
System.out.printf("A%n");
System.out.printf("B\n");
line.separator bytes: [10]
A
B
java NewlineDemo | tail -2 | od -c
0000000    A  \n   B  \n
0000004

Use %n inside printf. It is the portable one, and it costs nothing. Note that \n still works inside printf and %n does not work inside printlnprintln("A%n") prints the literal characters %n.

System.out and System.err

Both write to the terminal, so on screen they look the same. They are two different streams, and the shell can separate them:

java OutputDemo 2>/dev/null      # stdout only
java OutputDemo 2>&1 1>/dev/null # stderr only
ABC
next line
An is 25 years old
this went to stderr

Send prompts and results to System.out, and error messages to System.err. That way a user who pipes your program's output into a file still sees the errors on screen.

How Scanner reads input

import java.util.Scanner;

Scanner sc = new Scanner(System.in);

Everything else in this article follows from this one model:

One buffer, two reading modes: token methods versus nextLine

Scanner does not see lines, and it does not see keystrokes. It sees one long stream of characters, and it hands you pieces of that stream in two different ways:

  1. Token methodsnext(), nextInt(), nextDouble() and friends. A token is a run of characters bounded by the delimiter, which by default is any run of whitespace: spaces, tabs and line breaks all count. These methods skip leading whitespace, read one token, and stop immediately after the last character of that token.
  2. Line methodsnextLine(). This one ignores tokens entirely. It reads everything from the current position up to the next line break, returns it without the line break, and steps past the break.

Because whitespace is only a separator to the token methods, the same program accepts input laid out in completely different ways:

Scanner sc = new Scanner(System.in);
System.out.print("Three numbers: ");
int a = sc.nextInt();
int b = sc.nextInt();
int c = sc.nextInt();
System.out.printf("a=%d b=%d c=%d sum=%d%n", a, b, c, a + b + c);

Piping 10 20 30 on one line, 10, 20, 30 on three lines, or a mess of tabs and blank lines all give the same result:

Three numbers: a=10 b=20 c=30 sum=60

That is convenient, and it is also the root of every problem in the rest of this article. A token method reads the number and leaves the line break sitting in the stream, because a line break is just a separator to it. A line method then finds that separator and treats it as the whole line.

The Scanner methods, and what each one leaves behind

MethodReadsLeaves the cursor
next()one whitespace-delimited token, as a Stringright after the token
nextInt()one token, parsed as intright after the digits
nextLong()one token, parsed as longright after the digits
nextDouble()one token, parsed as doubleright after the number
nextBoolean()one token, true or false, case-insensitiveright after the token
nextLine()the rest of the current line, without the breakat the start of the next line
hasNext()nothing — reports whether a token existsunchanged
hasNextInt()nothing — reports whether the next token parses as intunchanged
hasNextLine()nothing — reports whether a line remainsunchanged

The hasNext* family is the important half of that table. Those methods consume nothing; they look ahead and return a boolean. That is what makes validation possible, and also what makes the infinite loop further down possible.

Here is a probe that prints the cursor's behaviour step by step. The input is four lines: 25, An Nguyen, Ha Noi Viet Nam, true 9007199254740993.

Scanner sc = new Scanner(System.in);

int n = sc.nextInt();
System.out.println("1. nextInt()   -> " + n);
System.out.println("   hasNextLine() = " + sc.hasNextLine());
System.out.println("2. nextLine()  -> [" + sc.nextLine() + "]   (the leftover newline)");
System.out.println("3. nextLine()  -> [" + sc.nextLine() + "]   (the real name)");
System.out.println("4. next()      -> [" + sc.next() + "]");
System.out.println("5. nextLine()  -> [" + sc.nextLine() + "]   (rest of that line)");
System.out.println("6. nextBoolean -> " + sc.nextBoolean());
System.out.println("7. nextLong    -> " + sc.nextLong());
System.out.println("8. hasNext()   = " + sc.hasNext());
1. nextInt()   -> 25
   hasNextLine() = true
2. nextLine()  -> []   (the leftover newline)
3. nextLine()  -> [An Nguyen]   (the real name)
4. next()      -> [Ha]
5. nextLine()  -> [ Noi Viet Nam]   (rest of that line)
6. nextBoolean -> true
7. nextLong    -> 9007199254740993
8. hasNext()   = false

Three things to take from that transcript. Step 2 returns an empty string, which is the trap. Step 4 shows next() reading only Ha, not the whole name, because a space ends a token. Step 5 shows the leading space in " Noi Viet Nam"nextLine() returns the rest of the line exactly, including the space that separated the tokens.

The nextLine trap

This is the bug. It has cost more beginner hours than anything else in this article.

How the Scanner cursor leaves a newline behind

Reproducing the bug

import java.util.Scanner;

public class Broken {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.print("Age: ");
        int age = sc.nextInt();

        System.out.print("Name: ");
        String name = sc.nextLine();

        System.out.println("age=" + age);
        System.out.println("name=[" + name + "]");
        System.out.println("length=" + name.length());
    }
}

Run it with the two lines 25 and An Nguyen:

printf '25\nAn Nguyen\n' | java Broken
Age: Name: age=25
name=[]
length=0

The age is right. The name is an empty string of length 0, and the program never waited for it. Typed at a real keyboard the effect is the same and looks worse: the Name: prompt flashes past and the program finishes before you can type.

The reason is exactly the model above. The input stream is 25\nAn Nguyen\n. nextInt() reads the token 25 and stops after the 5, leaving the cursor before the line break. nextLine() then does its job faithfully: it reads from the cursor to the end of the current line. Between the cursor and the end of that line there is nothing at all, so it returns "" and steps over the break. The name was never even reached.

Note the wording: nextLine() is not broken and it is not skipping anything. It read a line — the empty remainder of line 1.

Fix 1: consume the rest of the line

Call nextLine() once, throw the result away, and the cursor lands at the start of the real line:

System.out.print("Age: ");
int age = sc.nextInt();
sc.nextLine();                 // consume the rest of the number's line

System.out.print("Name: ");
String name = sc.nextLine();
Age: Name: age=25
name=[An Nguyen]

It works, and it is what most tutorials show. The cost is that you now have to remember a bare sc.nextLine(); after every token call that is followed by a line read. Miss one and the bug comes back, silently.

Fix 2: read lines only, parse the numbers yourself

Never mix the two families. Read every piece of input with nextLine(), then convert:

import java.util.Scanner;

public class FixTwo {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.print("Age: ");
        int age = Integer.parseInt(sc.nextLine().trim());

        System.out.print("Name: ");
        String name = sc.nextLine();

        System.out.println("age=" + age);
        System.out.println("name=[" + name + "]");
    }
}
Age: Name: age=25
name=[An Nguyen]

The .trim() matters: a user who types a trailing space would otherwise hand Integer.parseInt the string "25 ", which throws.

Which fix should you use?

Fix 2, for anything that reads from a human. The reasoning:

  • One mental model instead of two. Every read consumes exactly one line. There is no cursor state to keep in your head between statements.
  • It cannot silently regress. Fix 1 depends on a bare statement whose only purpose is a side effect; deleting it while tidying up reintroduces the bug and nothing complains.
  • It reads names with spaces. sc.next() would return An and leave Nguyen behind.
  • Better errors. A bad number gives you NumberFormatException at the exact line you parsed, not an InputMismatchException from somewhere inside Scanner.

Fix 1 is still fine for the case Scanner was designed for: whitespace-separated numeric input where you were never going to read a line of free text anyway.

What happens when the input is not a number?

nextInt() does not return an error code. It throws:

The hasNextInt guard loop, and the infinite loop when the bad token is not discarded

hasNextInt() only looks — forgetting to consume the bad token loops forever:

Scanner sc = new Scanner(System.in);
System.out.print("Age: ");
int age = sc.nextInt();
System.out.println("age=" + age);
printf 'abc\n' | java BadInput
Age: Exception in thread "main" java.util.InputMismatchException
	at java.base/java.util.Scanner.throwFor(Scanner.java:947)
	at java.base/java.util.Scanner.next(Scanner.java:1602)
	at java.base/java.util.Scanner.nextInt(Scanner.java:2267)
	at java.base/java.util.Scanner.nextInt(Scanner.java:2221)
	at BadInput.main(BadInput.java:7)

java.util.InputMismatchException means "a token is there, but it does not parse as what you asked for". Feeding 3.5 to nextInt() throws exactly the same thing. The token is still in the buffer afterwards — the exception was raised before Scanner advanced.

Exceptions are a topic of their own and a later article covers them properly. Here, treat try/catch and hasNextInt() as the two tools for one job: not letting bad input kill the program.

The validation loop

hasNextInt() reports whether the next token would parse, without consuming it. Loop while it is false, and discard the bad token with next() before checking again:

import java.util.Scanner;

public class Validate {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.print("Age: ");
        while (!sc.hasNextInt()) {
            String junk = sc.next();      // discard the offending token
            System.out.println(junk + " is not a whole number.");
            System.out.print("Age: ");
        }
        int age = sc.nextInt();

        System.out.println("age=" + age);
    }
}
printf 'abc\n3.5\n25\n' | java Validate
Age: abc is not a whole number.
Age: 3.5 is not a whole number.
Age: age=25

The infinite loop when you forget to discard

Drop the sc.next() and the loop has nothing to make progress with:

System.out.print("Age: ");
while (!sc.hasNextInt()) {
    System.out.println("Not a number, try again.");
    // BUG: the bad token is still sitting in the buffer
}
System.out.println("age=" + sc.nextInt());
printf 'abc\n25\n' | java InfiniteLoop
Age: Not a number, try again.
Not a number, try again.
Not a number, try again.
Not a number, try again.
Not a number, try again.

That output was truncated after five lines; the program does not stop on its own and has to be killed. hasNextInt() looks at abc, answers false, and changes nothing. The next iteration looks at the same abc. If your program ever floods the terminal with a validation message, this is why: something in the loop has to consume the input that failed the check.

NoSuchElementException: the input ran out

The other exception every beginner meets is what happens when there is no token left at all — you piped a file that ended, or you pressed Ctrl-D (Ctrl-Z then Enter on Windows) at the prompt:

printf '' | java BadInput
Age: Exception in thread "main" java.util.NoSuchElementException
	at java.base/java.util.Scanner.throwFor(Scanner.java:945)
	at java.base/java.util.Scanner.next(Scanner.java:1602)
	at java.base/java.util.Scanner.nextInt(Scanner.java:2267)
	at java.base/java.util.Scanner.nextInt(Scanner.java:2221)
	at BadInput.main(BadInput.java:7)

nextLine() at end of input throws the same class with a message: NoSuchElementException: No line found.

The two exceptions look similar and mean opposite things. InputMismatchException: there is input, it is the wrong shape. NoSuchElementException: there is no input left. Guard against the second with hasNext() or hasNextLine() before reading, which is the only reliable way to write a program that survives being run with its input redirected from a file.

The locale trap: is it 3.14 or 3,14?

Input parsing and output formatting are governed separately, so each side needs its own fix.

Two independent lanes: useLocale fixes nextDouble, while printf still needs a Locale of its own

nextDouble() parses using the JVM's default locale, and half the world writes decimals with a comma. This one is worth knowing precisely, because the same source file behaves differently on two machines:

Scanner sc = new Scanner(System.in);
System.out.println("default locale: " + Locale.getDefault());
System.out.print("Price: ");
double price = sc.nextDouble();
System.out.println("price=" + price);

Under a German locale, the string 3.14 is not a number:

printf '3.14\n' | java -Duser.language=de -Duser.country=DE LocaleDemo
default locale: de_DE
Price: Exception in thread "main" java.util.InputMismatchException
	at java.base/java.util.Scanner.throwFor(Scanner.java:947)
	at java.base/java.util.Scanner.next(Scanner.java:1602)
	at java.base/java.util.Scanner.nextDouble(Scanner.java:2573)
	at LocaleDemo.main(LocaleDemo.java:9)

And 3,14 is:

printf '3,14\n' | java -Duser.language=de -Duser.country=DE LocaleDemo
default locale: de_DE
Price: price=3.14

The Vietnamese locale behaves the same way — vi_VN also uses the comma as the decimal separator, so a program that works on a machine set to English fails on a machine set to Vietnamese and vice versa. Pin the locale on the Scanner and the input format stops depending on the machine:

Scanner sc = new Scanner(System.in);
sc.useLocale(Locale.US);            // "3.14" now parses everywhere
printf '3.14\n' | java -Duser.language=vi -Duser.country=VN LocaleFix
default locale: vi_VN
Price: price=3,14

Read that output carefully, because it shows the half of the problem most articles miss. Input parsing is now fixed — 3.14 was accepted under vi_VN. But the program printed 3,14, because printf formats with the default locale too, and useLocale only affects the Scanner. Formatter and Scanner are separate objects with separate locales:

System.out.printf("default : %.2f | %,d%n", 3.14159, 1234567);
System.out.printf(Locale.US, "US      : %.2f | %,d%n", 3.14159, 1234567);
java -Duser.language=de -Duser.country=DE PrintfLocale
default locale: de_DE
default : 3,14 | 1.234.567
US      : 3.14 | 1,234,567

So: scanner.useLocale(Locale.US) for input, and System.out.printf(Locale.US, ...) for output whenever the exact digits matter. If instead you want the user's own conventions everywhere, change nothing and let the default locale do its job — the mistake is assuming there is no locale involved at all.

Reading several values from one line, and useDelimiter

Several tokens on one line need no special handling, because whitespace is already the delimiter. Anything else does. useDelimiter replaces the whitespace pattern with a regular expression of your own — here a comma, applied to a Scanner over a single line of text:

Scanner line = new Scanner(System.in);
String csv = line.nextLine();

Scanner fields = new Scanner(csv);
fields.useDelimiter(",");
while (fields.hasNext()) {
    System.out.println("[" + fields.next() + "]");
}
fields.close();
printf 'An Nguyen,25,1250000\n' | java DelimiterDemo
[An Nguyen]
[25]
[1250000]

Note the trick in that code: read the line with nextLine(), then build a second Scanner over the resulting String. A Scanner can wrap any string, not only System.in, and doing it this way keeps the line boundary intact.

If you call useDelimiter(",") on the System.in Scanner directly, the line break stops being a delimiter and becomes an ordinary character inside the last token:

Scanner sc = new Scanner(System.in);
sc.useDelimiter(",");                 // the newline is now just a character
while (sc.hasNext()) {
    String t = sc.next();
    System.out.println("[" + t + "] length=" + t.length());
}
printf 'a,b,c\n' | java DelimiterTrap
[a] length=1
[b] length=1
[c
] length=2

The last token is c plus a line feed, length 2. Make the pattern accept both separators:

sc.useDelimiter("\\s*,\\s*|\\R");     // comma with optional spaces, or a line break
printf 'a, b ,c\nd,e\n' | java DelimiterFix
[a] length=1
[b] length=1
[c] length=1
[d] length=1
[e] length=1

\R is the regex escape for any line break, so this handles both Unix and Windows files.

Closing a Scanner closes System.in

Scanner implements Closeable, so try-with-resources works and closes it for you:

try (Scanner sc = new Scanner(System.in)) {
    System.out.print("Name: ");
    String name = sc.nextLine();
    System.out.println("hello " + name);
}

Here is the nuance nobody mentions. Closing a Scanner closes the stream underneath it, and the stream underneath is System.in — a JVM-wide object. Once it is closed, it stays closed for the rest of the program:

Scanner first = new Scanner(System.in);
System.out.println("first: " + first.nextLine());
first.close();

Scanner second = new Scanner(System.in);   // System.in is already closed
System.out.println("second: " + second.nextLine());
printf 'hello\nworld\n' | java CloseDemo
first: hello
Exception in thread "main" java.util.NoSuchElementException: No line found
	at java.base/java.util.Scanner.nextLine(Scanner.java:1660)
	at CloseDemo.main(CloseDemo.java:10)

The second Scanner is a perfectly valid object reading a dead stream. The same thing happens with the try-with-resources version — after the block, hasNextLine() on a fresh Scanner returns false instead of waiting for input.

⚠️ The practical rule: create exactly one Scanner over System.in per program, and do not close it. The JVM closes System.in at exit anyway. If a linter complains about an unclosed resource, close it once at the very end of main — never inside a helper method.

The rule is different for a Scanner over a String, or over a file: those own a resource that is worth closing, and try-with-resources is right for them.

Scanner vs BufferedReader vs System.console()

ScannerBufferedReaderSystem.console()
Constructionnew Scanner(System.in)new BufferedReader(new InputStreamReader(System.in))System.console()
Parsingbuilt in: nextInt, nextDoublenone — you call Integer.parseIntnone
Reads a linenextLine()readLine()readLine()
End of inputthrows NoSuchElementExceptionreturns nullreturns null
Bad numberInputMismatchException (unchecked)NumberFormatException where you parseas you parse
Checked exceptionsnonereadLine() throws IOExceptionnone
Locale-dependentyes, nextDouble and friendsno, you control parsingno
Under an IDE or with piped inputworksworksreturns null
Throughputlowesthighnot for bulk input
ExtrasuseDelimiter, hasNextIntjust linesreadPassword() hides typing

BufferedReader costs three more words to construct and gives you a much simpler model — one method, one line, null at the end:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class ReaderDemo {
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        System.out.print("Age: ");
        int age = Integer.parseInt(in.readLine().trim());

        System.out.print("Name: ");
        String name = in.readLine();

        System.out.println("age=" + age);
        System.out.println("name=[" + name + "]");
    }
}
printf '25\nAn Nguyen\n' | java ReaderDemo
Age: Name: age=25
name=[An Nguyen]

Notice there is no nextLine trap here, because there is only one way to read: whole lines. At end of input readLine() returns null rather than throwing, so while ((line = in.readLine()) != null) is the standard loop.

System.console() is the odd one out. It gives you readPassword(), which reads without echoing the characters, but it is only available when the program is attached to a real terminal. Piped input, redirected input, and most IDE run windows all give you null:

printf 'An\nsecret\n' | java ConsoleDemo
System.console() = null
no console: input is redirected, or running under an IDE

So System.console() is for command-line tools that ask for a password, and never for a program you want to test with a redirected input file.

On throughput: Scanner is slow, and it matters only when the input is large. Summing one million integers piped from a file, best of five runs after a warm-up, on this machine:

ApproachBest time
Scanner.hasNextInt() / nextInt()309 ms
BufferedReader.readLine() + Integer.parseInt48 ms

Roughly six times faster. The numbers are indicative and will differ on your hardware, but the ratio is why competitive programmers do not use Scanner. For a program that reads three values from a person, the difference is a rounding error and Scanner's convenience wins.

A complete example: prompt, validate, print a receipt

Everything above, in one program: line-based reading, validation with a retry, a guard against the input running out, and a locale-pinned formatted output.

import java.util.Locale;
import java.util.Scanner;

public class Receipt {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        String name = ask(sc, "Name: ");
        while (name.isEmpty()) {
            System.out.println("Name cannot be empty.");
            name = ask(sc, "Name: ");
        }

        int age;
        while (true) {
            try {
                age = Integer.parseInt(ask(sc, "Age: "));
                if (age > 0 && age < 130) break;
                System.out.println("Age must be between 1 and 129.");
            } catch (NumberFormatException e) {
                System.out.println("Age must be a whole number.");
            }
        }

        double price;
        while (true) {
            try {
                price = Double.parseDouble(ask(sc, "Price: "));
                if (price >= 0) break;
                System.out.println("Price cannot be negative.");
            } catch (NumberFormatException e) {
                System.out.println("Price must be a number, e.g. 12.50");
            }
        }

        System.out.println();
        System.out.printf(Locale.US, "%-12s %3s %12s%n", "CUSTOMER", "AGE", "PRICE");
        System.out.printf(Locale.US, "%-12s %3d %,12.2f%n", name, age, price);
    }

    private static String ask(Scanner sc, String prompt) {
        System.out.print(prompt);
        if (!sc.hasNextLine()) {
            System.out.println();
            System.out.println("Input ended unexpectedly.");
            System.exit(1);
        }
        return sc.nextLine().trim();
    }
}

Run it with input that gets three things wrong before getting them right:

printf 'An Nguyen\ntwenty\n25\nabc\n-5\n1250000.5\n' | java Receipt
Name: Age: Age must be a whole number.
Age: Price: Price must be a number, e.g. 12.50
Price: Price cannot be negative.
Price: 
CUSTOMER     AGE        PRICE
An Nguyen     25 1,250,000.50

The typed values do not appear in that transcript because the input came from a pipe rather than a keyboard, so nothing echoes them. What you see is the program's own output: the prompts, the three rejections, and the final two lines aligned by %-12s %3d %,12.2f.

And when the input runs out halfway, the hasNextLine() guard in ask produces a message instead of a stack trace:

printf 'An Nguyen\n' | java Receipt
Name: Age: 
Input ended unexpectedly.

FAQ

Why does my program skip the input where I ask for a name?

It did not skip it. A previous nextInt(), nextDouble() or next() left the line break in the buffer, and your nextLine() read the empty remainder of that earlier line. Either add a bare sc.nextLine(); after the token call, or read everything with nextLine() and parse the numbers with Integer.parseInt.

Can I create two Scanner objects over System.in?

You can, and they will fight over the same stream: each buffers ahead, so input read by one disappears before the other sees it. And if either one is closed, System.in is closed for both. Create one Scanner and pass it to whatever methods need it.

Why does nextDouble() fail on 3.14 on my machine?

Your JVM's default locale uses a comma as the decimal separator — vi_VN and de_DE both do. Call sc.useLocale(Locale.US) right after constructing the Scanner if you want 3.14 to parse regardless of the machine, and remember that printf formats with the default locale unless you pass Locale.US as its first argument.

How do I keep reading until the user is done?

Loop on a hasNext* method rather than a count: while (sc.hasNextInt()) for numbers, or while (sc.hasNextLine()) for lines. Both return false at end of input, which is Ctrl-D on Linux and macOS, Ctrl-Z then Enter on Windows, or simply the end of a redirected file. Reading past that point is what throws NoSuchElementException.

Is Scanner fast enough for competitive programming?

No. In the measurement above it took 309 ms to sum a million integers where BufferedReader took 48 ms, and problems with large input are usually designed with that gap in mind. Use BufferedReader with your own parsing for those. For ordinary programs that read a handful of values from a person, Scanner is the right tool.

Conclusion

Scanner's whole personality comes from one design decision: token methods and line methods read the same stream in two incompatible ways. Once you can picture the cursor stopping just before the line break, the empty string, the infinite loop and the NoSuchElementException all stop being surprises. Read whole lines and parse them yourself, guard with hasNextLine(), pin the locale when the digits matter, and do not close a Scanner over System.in.

Every program here ran straight through from top to bottom. The next article introduces the point where a program starts making choices: conditional statements — if, else if, else, and the switch statement.

Related Posts

[Java Basics] Recursion in Java: How It Works and When to Use It

How recursion works in Java: the base case and the recursive case, factorial traced frame by frame, a real StackOverflowError from a missing base case, recursion depth and -Xss, why naive Fibonacci needs 2692537 calls for fib(30) while memoisation needs 59, recursion versus iteration, and why the JVM does not optimise tail calls.

[Java Basics] Methods in Java: Declaring and Calling Them

How to declare and call a method in Java: the parts of a declaration, static versus instance methods and the non-static method cannot be referenced from a static context error, the return statement, the call stack and StackOverflowError, reading a stack trace, Javadoc, and the real compiler errors beginners hit.

[Java Basics] Exception Handling in Java: try, catch, finally, throw and throws

Exception handling in Java on OpenJDK 21: the Throwable hierarchy, try-catch-finally and what finally really guarantees, multi-catch and catch ordering, throw versus throws, checked versus unchecked exceptions, stack unwinding and how to read a stack trace, plus the traps — the empty catch, catch (Exception e), and return inside finally.

[Java Basics] HashMap in Java: put, get, merge and the hashCode/equals Contract

How HashMap works in Java - put, get, containsKey, remove, getOrDefault, putIfAbsent, merge and computeIfAbsent, iterating with entrySet, buckets and hash distribution, and the hashCode/equals contract that decides whether a key can be found again.