RizTech Academy logo
RizTech Academy
Language BasicsLesson 6 of 620 min

Reading input from the console

Every program so far has had its data typed into the source. That is fine for learning a for loop and useless for anything else — a program nobody can talk to is a program with one user.

This lesson makes your programs interactive. It is also where you meet the single most notorious beginner bug in Java, and rather than warning you about it afterwards, we are going to cause it on the second page.

Scanner

import java.util.Scanner;

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

        System.out.print("Customer name: ");
        String name = scanner.nextLine();

        System.out.println("Namaste, " + name);
    }
}

System.in is the input stream connected to your keyboard, and it delivers raw bytes. Scanner wraps it and turns those bytes into strings and numbers.

Note System.out.print rather than println for the prompt, so the cursor stays on the same line. That is the difference between a program that looks finished and one that does not.

The methods, and the distinction that matters

Method Reads Leaves behind
nextLine() Everything up to the newline Nothing — consumes the newline
next() One whitespace-separated token The rest of the line, including the newline
nextInt() One token, as an int The rest of the line, including the newline
nextLong() One token, as a long The rest of the line
nextDouble() One token, as a double The rest of the line
nextBoolean() true or false The rest of the line
hasNextLine() — Asks whether another line exists
hasNextInt() — Asks whether the next token parses as an int

Read that middle column twice. nextLine() consumes the newline; nothing else does. Every trap below comes from that one fact.

The bug

Scanner scanner = new Scanner(System.in);

System.out.print("Tiffins taken: ");
int tiffins = scanner.nextInt();

System.out.print("Customer name: ");
String name = scanner.nextLine();

System.out.println("tiffins = " + tiffins);
System.out.println("name    = [" + name + "]");

Type 26, press Enter, and then try to type a name:

Tiffins taken: 26
Customer name: tiffins = 26
name    = []

It never waited. The name is empty, and the program did not pause to ask.

Here is exactly what happened. You typed 26 and Enter, so the input buffer holds 26\n. nextInt() took the 26 and stopped, leaving the \n sitting there. nextLine() then read everything up to the next newline — which was immediately, with nothing before it. It returned an empty string without ever needing your keyboard.

Every mixed nextInt() / nextLine() program has this bug until it is fixed.

Two fixes, and only one of them is good.

The patch everybody writes, an extra nextLine() to swallow the leftover newline:

int tiffins = scanner.nextInt();
scanner.nextLine();                 // discard the rest of the line
String name = scanner.nextLine();

Works. But now every nextInt() needs a matching throwaway call, and forgetting one reintroduces the bug somewhere you are not looking.

The fix worth adopting: read lines, parse them yourself.

System.out.print("Tiffins taken: ");
int tiffins = Integer.parseInt(scanner.nextLine().strip());

System.out.print("Customer name: ");
String name = scanner.nextLine().strip();

Use nextLine() for everything, and convert with Integer.parseInt, Long.parseLong or Double.parseDouble. One method, one line of input per call, no hidden state. The strip() handles the spaces people type without noticing.

This is not a beginner's simplification — it is what production code does, because it also makes the error handling possible, which is the next section.

When the user types rubbish

They will. nextInt() on abc does this:

java.util.InputMismatchException

No message at all — getMessage() returns null. And the bad token is not consumed, so a naive retry loop spins forever on the same text.

Integer.parseInt fails more usefully:

java.lang.NumberFormatException: For input string: "abc"

That one tells you what it choked on, which matters when the input came from a file with four thousand rows.

Two ways to handle it. Ask before you read, using the hasNextInt family:

if (scanner.hasNextInt()) {
    int n = scanner.nextInt();
}

Or try to parse and catch the failure, which works with the read-lines approach and is the one that scales:

static int readInt(Scanner scanner, String prompt, int min, int max) {
    while (true) {
        System.out.print(prompt);
        String line = scanner.nextLine().strip();
        try {
            int value = Integer.parseInt(line);
            if (value < min || value > max) {
                System.out.println("  Enter a number between " + min + " and " + max + ".");
                continue;
            }
            return value;
        } catch (NumberFormatException e) {
            System.out.println("  [" + line + "] is not a whole number.");
        }
    }
}
Tiffins taken: twenty six
  [twenty six] is not a whole number.
Tiffins taken: 99
  Enter a number between 0 and 62.
Tiffins taken: 26
Customer name: Priya Deshmukh

try/catch is module 7's subject and this is the whole shape of it: attempt the risky thing, and say something useful when it fails. Two details worth copying. The range check is a separate message from the parse failure, because "99 is not a number" would be a lie. And the invalid input is echoed back in brackets, so an invisible trailing space is visible in the message.

Write readInt once and reuse it. Every command-line program you write from here on needs it, and the capstone has a version of exactly this.

Reading until there is no more

A program fed from a file or a pipe has to know when to stop:

while (scanner.hasNextLine()) {
    String line = scanner.nextLine();
    if (line.isBlank()) {
        continue;
    }
    process(line);
}

hasNextLine() returns false at end of input. Calling nextLine() anyway throws:

java.util.NoSuchElementException: No line found

That is what you get when a program expects input and is run with none — a common way for a working program to fail in a CI pipeline. On a terminal, end of input is Ctrl+D on macOS and Linux, Ctrl+Z then Enter on Windows.

This also means your program works with a pipe for free:

java Report.java < deliveries.txt

Which makes it testable without typing anything, and is worth knowing now.

Four practical points

Do not close a Scanner wrapping System.in. Closing it closes System.in itself, and any later Scanner throws NoSuchElementException. This is the one place where the usual "always close what you open" advice is wrong. Create one Scanner for the program and pass it around.

One Scanner per program. Two scanners on System.in will steal buffered input from each other, producing behaviour that looks random.

System.console() returns null in an IDE. It is the API for reading a password without echoing it, and IntelliJ does not provide a real console, so it fails only when run from the IDE and works from a terminal. Use Scanner unless you specifically need hidden input.

Locale affects nextDouble(). On a system configured for a locale that uses a comma as the decimal separator, nextDouble() rejects 3.14. Another reason to read lines and use Double.parseDouble, which always expects a dot.

Check your work

Why does nextLine() after nextInt() return an empty string? nextInt() consumes the digits and leaves the newline in the buffer. nextLine() reads up to the next newline, finds it immediately, and returns the empty text before it.

What are the two fixes, and which is better? An extra throwaway scanner.nextLine() after every numeric read, or reading everything with nextLine() and converting with Integer.parseInt. The second is better: it has no hidden state to forget and it gives you a catchable NumberFormatException.

What does nextInt() throw on abc, and why is that awkward? InputMismatchException, with a null message, and the offending token is left in the buffer — so a retry loop that does not consume it spins forever.

What does Integer.parseInt("abc") throw? NumberFormatException: For input string: "abc" — which names the input, so it is far more useful in a log.

What happens if you call nextLine() at end of input? NoSuchElementException: No line found. Guard with hasNextLine().

Why should you not close a Scanner on System.in? It closes System.in as well, so nothing can read input afterwards.

Practice 2, the two-question program. The working version reads both answers as lines:

Scanner scanner = new Scanner(System.in);

System.out.print("Tiffins taken: ");
int tiffins = Integer.parseInt(scanner.nextLine().strip());

System.out.print("Customer name: ");
String name = scanner.nextLine().strip();

System.out.printf("%s owes Rs %d.%02d%n", name, tiffins * 8235 / 100, tiffins * 8235 % 100);

Practice 4, the menu loop.

boolean running = true;
while (running) {
    System.out.println("""
            1  Add a delivery
            2  Show the total
            3  Quit""");
    String choice = scanner.nextLine().strip();
    switch (choice) {
        case "1" -> deliveries++;
        case "2" -> System.out.println("Deliveries: " + deliveries);
        case "3" -> running = false;
        default -> System.out.println("Unknown option [" + choice + "]");
    }
}

Switching on the String rather than parsing an int first is deliberate: an unknown option is then an ordinary default branch instead of an exception, and a blank line does not crash the program. The running flag beats break here because break inside a switch would leave the switch, not the loop — which is exactly the kind of quiet mistake the arrow form was introduced to prevent.

Practice

  1. Cause the bug. Write the nextInt() then nextLine() program and watch it skip the name. Then fix it both ways — with a throwaway nextLine(), and by reading lines and parsing. Keep the second.

  2. A two-question bill. Ask for a customer name and a number of tiffins, then print what they owe at Rs 82.35 each, formatted in rupees and paise. No double anywhere.

  3. Make readInt your own. Copy the version above, then extend it: allow a blank line to mean "use the default" and take that default as a parameter. Test it with a blank line, a negative number, 9999, 12abc and a line of spaces.

  4. A menu loop. 1 add a delivery, 2 show the total, 3 quit. Use do/while or a flag, handle an unknown option without exiting, and make sure pressing Enter on an empty line does not crash it.

  5. Read until end of input. Write a program that reads lines until there are none, ignores blank ones, and prints how many non-blank lines it saw and the longest. Run it twice: once by typing and ending with Ctrl+D, and once as java Count.java < somefile.txt. The second is how you will test every command-line program you write.

  6. Harder — a delivery log. Read lines of the form Priya,26 until end of input, one customer per line. Reject a line with the wrong number of fields or a non-numeric count by printing which line number was bad and carrying on rather than stopping. At the end, print each customer, their tiffin count, and the grand total. Carrying on after a bad row rather than dying is the difference between a script and a tool.


That is module two. You can declare and convert types without losing money to a double, handle strings without the == trap, use both forms of switch, work with arrays and know their limits, and write a program a person can actually use.

Next module: object-oriented Java — classes, records, enums, and the Java 21 pattern matching that makes them worth having.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship