Java Module 1 – Start here: JDK 25 LTS and your first programs
Reading input with IO.readln and Scanner
Read lines with IO.readln, turn text into numbers safely, read until the input ends, and use Scanner without the nextInt-then-nextLine trap.
What you will learn
- Read lines with IO.readln, detect the end of input and loop over every line
- Convert text with Integer.parseInt and Double.parseDouble, and check each result
- Read tokens with a Scanner in a fixed locale, and avoid the nextInt-then-nextLine trap
- Choose one reader for System.in, and a BufferedReader for very large inputs
Before you start
What you’ll learn · Before you start
What you will learn
- Read lines with IO.readln, detect the end of input and loop over every line
- Convert text with Integer.parseInt and Double.parseDouble, and check each result
- Read tokens with a Scanner in a fixed locale, and avoid the nextInt-then-nextLine trap
- Choose one reader for System.in, and a BufferedReader for very large inputs
Before you start
On this page
A program that adds up marks receives them as text: the line 72 is the two characters 7 and 2 until something
converts it, and the line twelve is no number at all. Reading input well means getting that text in, turning it into
values, and noticing at once when it is wrong or runs out.
On JDK 25 the simplest way to read standard input: The stream a program reads its input from, System.in in Java. It is the keyboard of the terminal unless the shell connects a file with < or the output of another command with |. is IO.readln(), which returns one
line at a time and null when the input ends; Integer.parseInt and Double.parseDouble turn a line into a number.
A Scanner reads numbers wherever the line breaks fall, and a BufferedReader reads large inputs fast. Each section
runs one of them on real input and shows the mistake it invites: a word where a number should be, a line break left
behind, two readers fighting over the same bytes, and a million numbers against a time limit.
Standard input reaches a program as text, through one reader
A program’s standard input is the keyboard of the terminal it runs in, unless the shell connects it to something
else: a file with <, as in java SumLines.java < numbers.txt, or the output of another command with |. That is how
an online judge or a test script feeds a program, and how every session on this page does it. When you type the input
yourself, the program waits at each read until you press Enter.
Java calls this stream System.in, and a program reads it through one of three readers:
Three ways to read standard input, and where the numbers come from
Text description of the diagram
The diagram runs from top to bottom.
- Standard input comes from the keyboard, or from a file or a pipe that the shell connects with < or |.
- It goes to one reader, chosen for the whole program, from three stacked in a box labelled "Pick one reader": IO.readln(), which returns one line as a String and null when the input has ended; Scanner, whose nextInt(), nextDouble() and next() read tokens, the pieces between spaces and line breaks; and BufferedReader, whose readLine() returns a line for the program to split, and which is fast on large inputs.
- The text that a reader returns goes to Integer.parseInt or Double.parseDouble, which turn it into a number for your program.
- A dashed arrow labelled "Scanner converts" goes from the readers straight to the program, because nextInt() and nextDouble() already return numbers.
Whichever reader you choose, it hands you text: IO.readln() gives the line 42 as the String "42", two
characters long. Integer.parseInt and Double.parseDouble turn such text into numbers; only a Scanner converts by
itself, which is the dashed arrow in the figure. The box around the readers says pick one: a program uses a single
reader for all its input, for a reason that the section on mixing readers shows with a run.
Checkpoint
Check: Standard input and its readers
IO.readln returns the next line, or null once the input has ended
IO.readln("Your age: ") prints its prompt, without a line break, and then reads one line. This program asks for a
name and an age, and keeps asking until the age is a whole number from 1 to 120:
// Asks for a name and an age, and asks again until the age is a whole number from 1 to 120.
void main() {
String name = IO.readln("Your name: ");
while (true) {
String text = IO.readln("Your age: ");
if (text == null) {
IO.println("The input ended before a valid age.");
return;
}
try {
int age = Integer.parseInt(text.strip());
if (age >= 1 && age <= 120) {
IO.println("Hello, " + name.strip() + ". Next year you will be " + (age + 1) + ".");
return;
}
IO.println(age + " is not an age from 1 to 120.");
} catch (NumberFormatException e) {
IO.println("Not a whole number: " + e.getMessage());
}
}
} from session import run
# Three wrong answers, then a good one; then an input that ends before any valid age.
run("""
cat age-input.txt
java AgeCheck.java < age-input.txt
java AgeCheck.java < age-short.txt
""") Output
# JDK in use: openjdk version "25.0.4.1" LTS $ cat age-input.txt Asha Rao nineteen 250 19 $ java AgeCheck.java < age-input.txt Your name: Your age: Not a whole number: For input string: "nineteen" Your age: 250 is not an age from 1 to 120. Your age: Hello, Asha Rao. Next year you will be 20. $ java AgeCheck.java < age-short.txt Your name: Your age: Not a whole number: For input string: "abc" Your age: The input ended before a valid age.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 age_session.py
Step through both runs and watch what the program does with each line it receives, and what it prints in reply:
The drawing shows the whole scene; its 11 steps are listed below.
Every step as text
- The first prompt. IO.readln("Your name: ") first prints its prompt, without a line break, so the answer you type would appear on the same line.
- The first line. Then it reads one line and returns it without its line break: the String "Asha Rao", which the program keeps as name.
- Text, not a number. After the prompt Your age:, the next line is nineteen. Integer.parseInt cannot turn it into an int and throws a NumberFormatException.
- Explain and ask again. The catch block prints the reason the exception gives, For input string: "nineteen", and the loop asks for the age again instead of stopping.
- A number out of range. The next line, 250, is a whole number, so parseInt converts it without complaint. The range check refuses it: converting and checking are two separate steps.
- Say why. The program prints 250 is not an age from 1 to 120. and asks once more.
- A space in front. The fourth line is 19 with a space in front. strip() removes the space before parseInt sees the text, and 19 passes the range check.
- Done. The program prints Hello, Asha Rao. Next year you will be 20. and returns, so it never asks for another line.
- A shorter input. The second run reads age-short.txt, which holds only Ravi and abc. The name is read, and abc fails to parse, as nineteen did.
- The input has ended. When the program asks for the age again, no line is left. IO.readln returns null, the sign that the input has ended.
- Stop, do not fail. The check text == null catches it: the program prints The input ended before a valid age. and returns. Without the check, text.strip() would fail with a NullPointerException.
What happened, line by line:
- The prompts and the answers look run together because the input came from a file: the program printed each prompt, then read a line without showing it. In a terminal your own typing appears after each prompt.
nineteenis not a number, soInteger.parseIntthrew aNumberFormatException. Thetryblock caught it, ande.getMessage()gave the reason,For input string: "nineteen". Module 8 covers exceptions in depth; here it is enough thatcatchlets the program explain the problem and ask again instead of stopping.250is a whole number, so the conversion worked; the range check refused it. Converting and checking are two separate steps, and a program needs both.19arrived with a space in front.strip()removed the space beforeparseIntsaw the text.- In the second run the input ended after
abc. The nextIO.readlnreturnednull, and the program said so instead of waiting for an answer that would never come. Without that check,text.strip()would fail with aNullPointerException.
The prompt goes to standard output, the same place as the program’s results, so a program that writes its results to
a file with > also writes its prompts there. Programs that read data rather than talk to a person usually call
IO.readln() without a prompt.
Key idea
IO.readln hands over each line as text and null at the end; parseInt then converts or throws, and only after
that can the program check whether the number makes sense.
Checkpoint
Check: Reading a line with IO.readln
parseInt takes a sign and digits; parseDouble takes far more
Most input bugs happen at the conversion, so it pays to know exactly which texts the two methods take. This program
gives the same thirteen texts to both of them, and prints refused wherever one threw a NumberFormatException:
// What Integer.parseInt and Double.parseDouble make of the same texts; "refused" means NumberFormatException.
void main() {
String[] texts = {"42", "+42", "-7", " 42", "4_2", "42.0", "", "2147483648", "२०२६", "3,5", "1e3", "NaN", "Infinity"};
IO.println(String.format("%-14s%-12s%s", "text", "parseInt", "parseDouble"));
for (String text : texts) {
IO.println(String.format("%-14s%-12s%s", "\"" + text + "\"", asInt(text), asDouble(text)));
}
}
String asInt(String text) {
try {
return String.valueOf(Integer.parseInt(text));
} catch (NumberFormatException e) {
return "refused";
}
}
String asDouble(String text) {
try {
return String.valueOf(Double.parseDouble(text));
} catch (NumberFormatException e) {
return "refused";
}
} from session import run
# Thirteen texts, each given to Integer.parseInt and to Double.parseDouble.
run("""
java Parse.java
""") Output
# JDK in use: openjdk version "25.0.4.1" LTS $ java Parse.java text parseInt parseDouble "42" 42 42.0 "+42" 42 42.0 "-7" -7 -7.0 " 42" refused 42.0 "4_2" refused refused "42.0" refused 42.0 "" refused refused "2147483648" refused 2.147483648E9 "२०२६" 2026 refused "3,5" refused refused "1e3" refused 1000.0 "NaN" refused NaN "Infinity" refused Infinity
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 parse_session.py
Read the table one column at a time:
Integer.parseIntaccepts an optional+or-followed by digits, nothing else. A space at either end, the underscore that Java source code allows in4_2, a decimal point and the empty string are all refused.2147483648is refused too: it is one more thanInteger.MAX_VALUE, andparseIntreports a number that does not fit anintinstead of wrapping it around.parseDoublereads it as2.147483648E9.२०२६, written in Devanagari digits, became2026. According to its documentation,parseIntaccepts every character thatCharacter.digitcounts as a digit, and that covers the decimal digits of many scripts, not only0to9.Double.parseDoublerefused it: it takes only0to9, and its documentation points toNumberFormatfor other digits.Double.parseDoubleremoves spaces at both ends by itself, reads1e3as1000.0, and refuses the comma of3,5.- It also accepts
NaN(“not a number”) andInfinity. A program that reads measurements should therefore check the result withDouble.isFinite(value)as well as its range.
Two habits follow. Call strip() on a line before converting it, and treat every conversion as something that can
fail: catch NumberFormatException where the text comes in, and say which input was wrong.
Converting a line exactly as it was typed
Integer.parseInt(line) refuses 19, a 19 typed after a space, and the program rejects a perfectly good answer.
Integer.parseInt(line.strip()) gives 19, because strip() removes the spaces at both ends first.
Checkpoint
Check: What parseInt and parseDouble accept
A loop reads every line until IO.readln returns null
Many inputs do not say how many lines follow, so a program reads until the end of input: The point where standard input has no characters left: a file has been read to its end, or the user pressed Ctrl-D (macOS, Linux) or Ctrl-Z and Enter (Windows). IO.readln then returns null., when IO.readln() returns null. This one adds up whole numbers, one per line:
// Adds up the whole numbers on standard input, one per line, until the input ends.
void main() {
long total = 0;
int count = 0;
int lineNumber = 0;
String line;
while ((line = IO.readln()) != null) {
lineNumber++;
String text = line.strip();
if (text.isEmpty()) {
continue;
}
try {
total += Integer.parseInt(text);
count++;
} catch (NumberFormatException e) {
System.err.println("line " + lineNumber + ": \"" + line + "\" is not a whole number, skipped");
}
}
IO.println(count + " numbers, total " + total);
} from session import run
# The same program with a file, a pipe and an empty input.
run("""
cat numbers.txt
java SumLines.java < numbers.txt
printf '2\\n3' | java SumLines.java
java SumLines.java < /dev/null
""") Output
# JDK in use: openjdk version "25.0.4.1" LTS $ cat numbers.txt 12 30 8 twelve 50 $ java SumLines.java < numbers.txt line 5: "twelve" is not a whole number, skipped 4 numbers, total 100 $ printf '2\n3' | java SumLines.java 2 numbers, total 5 $ java SumLines.java < /dev/null 0 numbers, total 0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 sum_session.py
The loop condition (line = IO.readln()) != null does two jobs: it stores the next line in line, and it stops the
loop when there is none. Step through the first run, one call of IO.readln at a time, and watch the total:
The drawing shows the whole scene; its 9 steps are listed below.
Every step as text
- Line 1. The condition (line = IO.readln()) != null stores "12" in line and lets the body run. strip() changes nothing, and parseInt adds 12 to the total.
- Line 2. The next call returns "30", and the total becomes 42.
- A blank line. Line 3 is empty. strip() leaves "", isEmpty() is true, and continue goes straight back to the condition: nothing is added.
- A space in front. Line 4 is 8 with a space in front. strip() removes the space, and the total becomes 50.
- Not a number. Line 5 is twelve. parseInt throws a NumberFormatException, and the catch block reports the line instead of adding it.
- To standard error. The report goes to standard error with System.err.println, so it stays on the screen even when the results are saved to a file with >.
- Line 6. The last line, 50, makes the total 100 and the count 4.
- The input has ended. No line is left, so IO.readln returns null. The condition is false, and the loop ends without running its body again.
- The result. After the loop the program prints its one line of results, 4 numbers, total 100, on standard output.
Inside the loop, a blank line is skipped with continue, and a line that is not a number is reported on
The second output stream of a program, System.err in Java, meant for error messages. It reaches the terminal even when standard output is redirected to a file with >.
with its line number (System.err.println), so the message stays visible even when the results are saved with >.
Line 5, twelve, was skipped, and the other four numbers added up to 100.
The second run shows that the last line counts even without a line break after it: printf '2\n3' sends no final
line break, and both numbers arrived. The third run reads from /dev/null, an input that is empty from the start, and
the very first call returned null.
When you type the input yourself, you tell the program it has ended with a key: Ctrl-D in a macOS or Linux terminal,
Ctrl-Z followed by Enter in a Windows console. Feeding a file with < works in macOS and Linux shells and in the
Windows Command Prompt. PowerShell keeps < reserved for future use, so there Get-Content numbers.txt | java SumLines.java does the same job.
Checkpoint
Check: Reading until the input ends
A Scanner reads tokens, wherever the line breaks fall
A Scanner splits its input into token: A piece of input between two stretches of whitespace (spaces, tabs or line breaks), such as 72 or eighty. A Scanner reads one token with next(), or converts it with nextInt() or nextDouble(). wherever there is whitespace (spaces, tabs or line
breaks) and converts each token when you ask for it. nextInt() returns the next token as an int, nextDouble() as
a double and next() as a String. Because line breaks are just whitespace to it, a count followed by that many
numbers can be laid out in any way:
// Reads a count, then that many marks, wherever the line breaks fall.
void main() {
Scanner in = new Scanner(System.in, System.getProperty("stdin.encoding"));
int n = in.nextInt();
int sum = 0;
for (int i = 0; i < n; i++) {
sum += in.nextInt();
}
IO.println(n + " marks, sum " + sum);
} from session import run
# The same five marks on two lines and on one line; then a word among the numbers, and too few numbers.
run("""
printf '5\\n72 85\\n64 91 57\\n' | java Tokens.java
printf '5 72 85 64 91 57\\n' | java Tokens.java
printf '3\\n72 eighty 64\\n' | java Tokens.java
printf '3\\n72 64\\n' | java Tokens.java
""", expect_failure=[
"printf '3\\n72 eighty 64\\n' | java Tokens.java",
"printf '3\\n72 64\\n' | java Tokens.java",
]) Output
# JDK in use: openjdk version "25.0.4.1" LTS $ printf '5\n72 85\n64 91 57\n' | java Tokens.java 5 marks, sum 369 $ printf '5 72 85 64 91 57\n' | java Tokens.java 5 marks, sum 369 $ printf '3\n72 eighty 64\n' | java Tokens.java Exception in thread "main" java.util.InputMismatchException at java.base/java.util.Scanner.throwFor(Scanner.java:977) at java.base/java.util.Scanner.next(Scanner.java:1632) at java.base/java.util.Scanner.nextInt(Scanner.java:2297) at java.base/java.util.Scanner.nextInt(Scanner.java:2251) at Tokens.main(Tokens.java:7) [exit status 1] $ printf '3\n72 64\n' | java Tokens.java Exception in thread "main" java.util.NoSuchElementException at java.base/java.util.Scanner.throwFor(Scanner.java:975) at java.base/java.util.Scanner.next(Scanner.java:1632) at java.base/java.util.Scanner.nextInt(Scanner.java:2297) at java.base/java.util.Scanner.nextInt(Scanner.java:2251) at Tokens.main(Tokens.java:7) [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 tokens_session.py
The first two runs printed the same 5 marks, sum 369 for five marks on two lines and on one line. Step through the
first of them, one call of nextInt() at a time, and then the two inputs on which the program stopped:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- The input arrives. The Scanner reads standard input and splits it into tokens wherever there is whitespace: spaces, tabs and line breaks alike.
- The count. The first nextInt() returns the token 5, converted to an int, so the loop will read five marks.
- Across a line break. The next call skips the line break after 5, which is only whitespace to the Scanner, and returns 72.
- The same line. The third call returns 85, the second token of line 2.
- The next line. The line break after 85 is skipped the same way, and the call returns 64 from line 3.
- The last two. Two more calls return 91 and 57. The loop has read its five marks, and the program prints 5 marks, sum 369.
- A word among the numbers. On the input 3, then 72 eighty 64, the second mark is the token eighty. nextInt() throws an InputMismatchException and leaves eighty unread.
- Too few numbers. On the input 3, then 72 64, the third nextInt() finds no token at all and throws a NoSuchElementException.
On eighty the Scanner threw an InputMismatchException, with no message at all; the documentation adds that the
Scanner leaves such a token unread, so a loop that simply tries nextInt() again meets it again. When the input
ended after two of three numbers, it threw a NoSuchElementException. To test before reading, use hasNextInt(),
which says whether the next token is a whole number without reading it, and next() to skip a token that is not.
The Scanner is created with new Scanner(System.in, System.getProperty("stdin.encoding")). The second argument names
the The rule that turns characters into bytes and back again, such as UTF-8. Bytes decoded with the wrong one come out as the wrong characters.
of standard input. Without it, a Scanner decodes the bytes with the default charset, which is UTF-8 since JDK 18
(JEP 400). The documentation of System.in asks programs to decode it with the encoding that the stdin.encoding
property names instead. Standard input is the keyboard or another source that the host environment or the user sets
up, and stdin.encoding is the name of its encoding. IO.readln uses stdin.encoding by itself.
Checkpoint
Check: Tokens and the Scanner
A Scanner reads numbers the way its locale writes them
A Scanner also reads numbers the way its A setting that names a language and a region, such as de-DE for German in Germany or en-IN for English in India, and decides how numbers, dates and text are written there. writes them. Its initial locale is the computer’s format locale, so the same program can read the same file differently on two computers:
// How a Scanner reads the same four texts under three locales.
void main() {
Locale[] locales = {Locale.ROOT, Locale.of("en", "IN"), Locale.GERMANY};
for (String text : new String[] {"3.5", "3,5", "100,000", "1,00,000"}) {
List<String> results = new ArrayList<>();
for (Locale locale : locales) {
Scanner in = new Scanner(text).useLocale(locale);
String value = in.hasNextInt() ? "int " + in.nextInt()
: in.hasNextDouble() ? "double " + in.nextDouble()
: "no number";
results.add(locale.toLanguageTag() + ": " + value);
}
IO.println(String.format("%-9s", text) + String.join(" | ", results));
}
} from session import run
# und is Locale.ROOT, the locale of no country and no language.
run("""
java LocaleNumbers.java
""") Output
# JDK in use: openjdk version "25.0.4.1" LTS $ java LocaleNumbers.java 3.5 und: double 3.5 | en-IN: double 3.5 | de-DE: no number 3,5 und: no number | en-IN: no number | de-DE: double 3.5 100,000 und: int 100000 | en-IN: int 100000 | de-DE: double 100.0 1,00,000 und: no number | en-IN: no number | de-DE: no number
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 locale_session.py
Each row is one text and each column one locale. In the German locale (de-DE), where the decimal separator is a
comma, 3.5 was no number at all and 3,5 became 3.5. Locale.ROOT (shown as und) and en-IN accepted a comma
between groups of three digits, so 100,000 became the whole number 100000; in German it became the decimal 100.0. For
data written with a decimal point, call useLocale(Locale.ROOT) on the Scanner, so that it reads the same text the
same way on every computer.
Indian number formats
None of the three locales read 1,00,000, the way one lakh is written in India, because a Scanner accepts group
separators only between groups of three digits. Integer.parseInt refuses every comma. If a file or a form holds
Indian-style grouping, remove the commas before converting, for example with text.replace(",", ""), and then check
the result’s range as usual.
Checkpoint
Check: A Scanner's locale
nextLine() after nextInt() returns the rest of the line, often empty
Reading a number with nextInt() and then a whole line with nextLine() is the most common Scanner bug of all:
// An age with nextInt(), then a full name with nextLine(): the classic trap.
void main() {
Scanner in = new Scanner(System.in, System.getProperty("stdin.encoding"));
IO.print("Age: ");
int age = in.nextInt();
IO.print("Full name: ");
String name = in.nextLine();
IO.println("Hello, [" + name + "], age " + age);
} Fix 1: TrapFixed.java · TrapFixed.java
// Fix 1: after nextInt(), one nextLine() call throws away the rest of that line.
void main() {
Scanner in = new Scanner(System.in, System.getProperty("stdin.encoding"));
IO.print("Age: ");
int age = in.nextInt();
in.nextLine();
IO.print("Full name: ");
String name = in.nextLine();
IO.println("Hello, [" + name + "], age " + age);
} Fix 2: LinesOnly.java · LinesOnly.java
// Fix 2: read whole lines only, and turn the one that holds a number into a number.
void main() {
int age = Integer.parseInt(IO.readln("Age: ").strip());
String name = IO.readln("Full name: ");
IO.println("Hello, [" + name + "], age " + age);
} from session import run
# One input file for all three programs: an age on the first line, a full name on the second.
run("""
cat age-name.txt
java Trap.java < age-name.txt
java TrapFixed.java < age-name.txt
java LinesOnly.java < age-name.txt
""") Output
# JDK in use: openjdk version "25.0.4.1" LTS $ cat age-name.txt 19 Asha Rao $ java Trap.java < age-name.txt Age: Full name: Hello, [], age 19 $ java TrapFixed.java < age-name.txt Age: Full name: Hello, [Asha Rao], age 19 $ java LinesOnly.java < age-name.txt Age: Full name: Hello, [Asha Rao], age 19
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 trap_session.py
The input is 19 on one line and Asha Rao on the next, yet Trap.java printed an empty name, []. Step through
the input as the Scanner sees it, two lines and their line breaks, and watch where each call stops:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The input. The file holds two lines, 19 and Asha Rao, each ended by a line break. The Scanner starts in front of the 1 of 19.
- nextInt(). nextInt() reads the token 19, returns the int 19 and stops right after it, in front of the first line break.
- nextLine(). nextLine() returns the rest of the current line and steps over its line break. The rest of line 1 is empty, so it returns "" at once.
- The trap. Trap.java prints Hello, [], age 19 and ends. Asha Rao was never read; the square brackets make the empty name visible.
- Fix 1. TrapFixed.java makes the same two calls and throws the empty string away, then calls nextLine() once more. That call returns Asha Rao, the whole of line 2.
- Fix 2. LinesOnly.java never stops in the middle of a line: IO.readln returns "19", which parseInt converts, and the next IO.readln returns Asha Rao.
nextInt() read the token 19 and stopped right after it, in front of the line break. nextLine() returns the rest
of the current line, and the rest of that line was empty, so it returned an empty string at once. The square brackets
are there to make the empty string visible.
Fix 1 calls nextLine() once more right after nextInt(), which throws away the rest of the line that held the
number. Fix 2 never mixes the two styles: it reads every line as a line and converts the one that holds a number.
Fix 2 is usually the better habit, because each line arrives whole, and a bad one can be reported as it was typed.
Key idea
After nextInt() the line break is still waiting, so the next nextLine() returns what is left of that line:
throw it away with one more nextLine(), or read whole lines only.
Checkpoint
Check: The nextInt-then-nextLine trap
Two readers on one System.in lose data without an error
Each of the three readers reads ahead: it fetches a large block of bytes and keeps what it has not returned yet in a
A block of memory where a reader keeps the bytes or characters it has fetched but not yet returned, so that it can read its source in large pieces.
of its own. That is why a program must not start with one reader and continue with another. The documentation of IO
is blunt about it: once a program has called a readln method, nothing is promised about what any other reader of
System.in gets. Here is what that looks like:
// Reads a title with IO.readln, then hands System.in to a Scanner for the numbers that follow: a mistake.
void main() {
String title = IO.readln();
Scanner rest = new Scanner(System.in, System.getProperty("stdin.encoding"));
int count = 0;
long first = -1;
while (rest.hasNextLong()) {
long n = rest.nextLong();
if (count == 0) {
first = n;
}
count++;
}
IO.println("title: " + title);
IO.println("numbers the Scanner found: " + count + ", the first: " + first);
} from session import run
# A small input, then a title followed by the 20,000 numbers 0 to 19999, one per line.
run("""
java Mixed.java < class-7b.txt
(echo Marks; seq 0 19999) > marks-big.txt
java Mixed.java < marks-big.txt
""") Output
# JDK in use: openjdk version "25.0.4.1" LTS $ java Mixed.java < class-7b.txt title: Marks of class 7B numbers the Scanner found: 0, the first: -1 $ (echo Marks; seq 0 19999) > marks-big.txt $ java Mixed.java < marks-big.txt title: Marks numbers the Scanner found: 18141, the first: 859
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 mixed_session.py
With the small file, the Scanner found no numbers at all: IO.readln had read the whole file into its buffer while
fetching the first line. With a title followed by the 20,000 numbers from 0 to 19999, the Scanner found only 18,141
of them, and the first number it read was 859: not the 0 that follows the title, but the last three digits of the
line 1859. In this run IO.readln had already taken the first 8,192 bytes, which ended in the middle of 1859.
Nothing failed or warned; the program simply computed with the wrong data. Step through where each reader’s bytes came
from:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- One block. To return the first line, IO.readln reads a block of bytes from System.in, not one line: 8,192 bytes, which hold the title, the numbers 0 to 1858 and the 1 of 1859.
- The title. It returns the line Marks and keeps the other 8,186 bytes in a buffer of its own, for readln calls that never come.
- A second reader. new Scanner(System.in) reads the same stream, which now continues where the block ended, in the middle of 1859.
- A wrong value. The first token the Scanner finds is 859, the last three digits of 1859: a number that is not in the file at all.
- Lost values. It counts 18,141 numbers, 859 and then 1860 to 19999. The numbers 0 to 1858 stay in the buffer of IO.readln, and nothing fails or warns.
- A small file. class-7b.txt has only 33 bytes, so the first block takes the whole file. The Scanner finds no number at all, and the program prints 0 and -1.
The cure is to choose one reader when the program starts and use it for all input. A Scanner can read lines too, with
nextLine(), and a BufferedReader reads lines with readLine().
Key idea
Every reader buffers more than it returns, so one reader owns System.in for the whole run: a second reader starts
wherever the first one’s block happened to end.
Checkpoint
Check: One reader for System.in
A BufferedReader reads a million numbers several times faster
A Scanner matches every token against
A pattern that describes text, such as one or more digits with an optional sign in front. A Scanner checks every token it reads against such patterns.,
which costs time on large inputs. A BufferedReader returns whole lines instead, and the program cuts each line into
numbers itself: with a StringTokenizer, the classic choice in contest code, or with String.split, which the
documentation of StringTokenizer recommends for new code because StringTokenizer is a legacy class. This program
reads the same million numbers in all three ways and times only the reading:
// Reads every whole number on standard input in one of three ways, and times only the reading.
void main(String[] args) throws IOException {
String how = args[0];
String charset = System.getProperty("stdin.encoding");
long start = System.nanoTime();
long sum = 0;
int count = 0;
if (how.equals("scanner")) {
Scanner in = new Scanner(System.in, charset);
while (in.hasNextInt()) {
sum += in.nextInt();
count++;
}
} else {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in, charset));
String line;
while ((line = in.readLine()) != null) {
if (how.equals("tokenizer")) {
StringTokenizer tokens = new StringTokenizer(line);
while (tokens.hasMoreTokens()) {
sum += Integer.parseInt(tokens.nextToken());
count++;
}
} else {
for (String token : line.split(" ")) {
if (!token.isEmpty()) {
sum += Integer.parseInt(token);
count++;
}
}
}
}
}
long ms = (System.nanoTime() - start) / 1_000_000;
IO.println(how + ": " + count + " numbers, sum " + sum + ", " + ms + " ms");
} // Writes count whole numbers (1,000 per line) from a fixed seed, so every run makes the same file.
void main(String[] args) {
int count = Integer.parseInt(args[0]);
Random random = new Random(25);
StringBuilder line = new StringBuilder();
for (int i = 1; i <= count; i++) {
line.append(random.nextInt(-1_000_000, 1_000_001));
if (i % 1000 == 0 || i == count) {
IO.println(line);
line.setLength(0);
} else {
line.append(' ');
}
}
} from session import run
# One million numbers in a 7 MB file, read twice in each of three ways.
run("""
java MakeInput.java 1000000 > million.txt
java ReadSpeed.java scanner < million.txt
java ReadSpeed.java scanner < million.txt
java ReadSpeed.java tokenizer < million.txt
java ReadSpeed.java tokenizer < million.txt
java ReadSpeed.java split < million.txt
java ReadSpeed.java split < million.txt
""") Output
# JDK in use: openjdk version "25.0.4.1" LTS $ java MakeInput.java 1000000 > million.txt $ java ReadSpeed.java scanner < million.txt scanner: 1000000 numbers, sum 1250132008, 463 ms $ java ReadSpeed.java scanner < million.txt scanner: 1000000 numbers, sum 1250132008, 430 ms $ java ReadSpeed.java tokenizer < million.txt tokenizer: 1000000 numbers, sum 1250132008, 70 ms $ java ReadSpeed.java tokenizer < million.txt tokenizer: 1000000 numbers, sum 1250132008, 130 ms $ java ReadSpeed.java split < million.txt split: 1000000 numbers, sum 1250132008, 95 ms $ java ReadSpeed.java split < million.txt split: 1000000 numbers, sum 1250132008, 91 ms
This output changes from run to run: The times depend on the computer and on what else it was doing; the count and the sum are the same in every run.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 speed_session.py
All three found the same million numbers with the same sum. On an Apple M5 Pro, the Scanner took 400 to 900 ms over
repeated runs, while the two BufferedReader versions took 60 to 300 ms, with no clear winner between
StringTokenizer and split: the Scanner was several times slower in every run. Side by side:
| Reader | How it gets the numbers | A million numbers, repeated runs |
|---|---|---|
Scanner |
nextInt() finds and converts each token |
400 to 900 ms |
BufferedReader and StringTokenizer |
readLine(), then the line cut into tokens |
60 to 300 ms |
BufferedReader and split |
readLine(), then line.split(" ") |
60 to 300 ms |
For a few lines typed at a keyboard the difference does not matter. For a judge’s input of hundreds of thousands of
numbers under a time limit of one or two seconds it can decide the verdict, and that is when a BufferedReader is
worth its few extra lines. The lesson on fast input and output, in the module on
DSA and placements, builds faster readers still.
Choosing a reader
Use IO.readln() for lines a person types, a Scanner for numbers spread over lines in any way, and a
BufferedReader with split when the input runs to hundreds of thousands of numbers.
Checkpoint
Check: Reading large inputs fast
Practise: a marks summary
Complete Marks.java so that it reads a count n and then n marks from 0 to 100, which may be spread over several
lines with blank lines between them, and prints the lowest mark, the highest mark and the average with two decimals.
For the input 5, then 72 85 64 91 57, it prints exactly:
Lowest: 57Highest: 91Average: 73.80
For n = 0 it prints No marks. A mark below 0 or above 100 is an error: print a message to standard error and stop
with System.exit(1).
// Exercise: read a count n, then n marks from 0 to 100, and print the lowest, the highest and the average.
// Replace the line below with your solution.
void main() {
IO.println("TODO");
} The session runs three sample tests, each comparing what the program printed with the expected output through diff,
as in the earlier exercises. As given, the program prints TODO, so all three fail:
from session import run
# The sample tests: run Marks.java on three inputs and compare each output with the expected one.
TESTS = """
java Marks.java < marks-five.txt > got.txt; diff expected-five.txt got.txt && echo PASS
java Marks.java < marks-blank-lines.txt > got.txt; diff expected-blank-lines.txt got.txt && echo PASS
java Marks.java < marks-none.txt > got.txt; diff expected-none.txt got.txt && echo PASS
"""
run(TESTS, expect_failure=[line.strip() for line in TESTS.strip().splitlines()]) Output
# JDK in use: openjdk version "25.0.4.1" LTS $ java Marks.java < marks-five.txt > got.txt; diff expected-five.txt got.txt && echo PASS 1,3c1 < Lowest: 57 < Highest: 91 < Average: 73.80 --- > TODO [exit status 1] $ java Marks.java < marks-blank-lines.txt > got.txt; diff expected-blank-lines.txt got.txt && echo PASS 1,3c1 < Lowest: 0 < Highest: 100 < Average: 50.00 --- > TODO [exit status 1] $ java Marks.java < marks-none.txt > got.txt; diff expected-none.txt got.txt && echo PASS 1c1 < No marks --- > TODO [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 marks_session.py
First hint: a Scanner does not care about blank lines or how the marks are spread, so nextInt() in a for loop
reads them all. Divide as double, not as int, and format the average with
String.format(Locale.ROOT, "Average: %.2f", average), so that the decimal separator is a point on every computer.
Then check all nine ideas in one short session. It picks the idea you have answered least well so far, asks one question at a time, and shows why each answer is right or wrong:
Practice that adapts to you
Practise this lesson
19 questions about 9 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Fresher · Why does nextLine() return an empty string right after nextInt(), and how do you fix it?
nextInt() reads only the digits of the number and stops in front of the line break that follows them. nextLine()
returns the rest of the current line, and after a number at the end of a line that rest is empty, so it returns ""
at once instead of waiting for the next line. There are two fixes. Call nextLine() once right after nextInt() to
throw away the end of that line, or read every line with nextLine() (or IO.readln()) and convert the number with
Integer.parseInt(line.strip()). The second is usually better: input arrives line by line, a bad line can be reported
as it was typed, and the two reading styles are never mixed. The same trap exists with nextDouble() and next().
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
Your Java solution reads 200,000 lines of numbers with a Scanner and gets "time limit exceeded" on a judge, although its algorithm is O(n log n) and it passes every small test. How do you find out whether reading the input is the bottleneck, and what would you change?
-
Fresher to mid
A program reads a header line with IO.readln() and then wraps System.in in a Scanner for the data. With small test files the Scanner finds no data at all; with a large file the first few thousand records are missing and one value is wrong. Explain the cause, and how you would make the input code safe.
Key takeaways
IO.readln()hands over one line per call, minus its line break, andnullat the end of input, so a loop that compares each result withnullreads every line.- A line is text: convert it with
Integer.parseInt(line.strip())orDouble.parseDouble(line), catchNumberFormatException, and check the range andDouble.isFiniteas separate steps. - A Scanner reads tokens across line breaks, with
System.getProperty("stdin.encoding")for the bytes anduseLocale(Locale.ROOT)for data written with a decimal point. - After
nextInt()the line break is still waiting: discard it with onenextLine(), or read whole lines only. - One reader owns
System.in: mixingIO.readlnwith a Scanner silently lost 1,859 numbers and read one wrong. - For very large inputs, a
BufferedReaderthat cuts each line withsplitor aStringTokenizerread a million numbers several times faster than a Scanner.
Terms in this lesson
- end of input
- The point where standard input has no characters left: a file has been read to its end, or the user pressed Ctrl-D (macOS, Linux) or Ctrl-Z and Enter (Windows). IO.readln then returns null.
- standard input
- The stream a program reads its input from, System.in in Java. It is the keyboard of the terminal unless the shell connects a file with < or the output of another command with |.
- token
- A piece of input between two stretches of whitespace (spaces, tabs or line breaks), such as 72 or eighty. A Scanner reads one token with next(), or converts it with nextInt() or nextDouble().
References
- Java SE 25 API: java.lang.IO (Oracle)
- Java SE 25 API: java.util.Scanner (Oracle)
- Java SE 25 API: java.io.BufferedReader (Oracle)
- Java SE 25 API: java.util.StringTokenizer (Oracle)
- Java SE 25 API: Integer.parseInt (Oracle)
- Java SE 25 API: Double.valueOf and Double.parseDouble (Oracle)
- Java SE 25 API: System.in and the stdin.encoding property (Oracle)
- JEP 400: UTF-8 by Default (OpenJDK)
- about_Redirection (PowerShell) (Microsoft)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress