Reading Input in Java: Scanner and Console

Scanner is Java's flexible tokenising reader: it reads from stdin, files, strings, or any InputStream, parses tokens by whitespace or a custom delimiter pattern, and provides typed accessors like nextInt() and nextDouble(). Console is a simpler, JVM-provided interface to the terminal that uniquely supports echo-free password input via readPassword(). This article covers both APIs, the common nextLine-after-nextInt pitfall, reading files with Scanner, and when to prefer BufferedReader instead.

Reading from Standard Input with Scanner

Scanner was designed to make reading structured input easy. Before it existed, reading an integer from standard input required a BufferedReader, a readLine() call, and an explicit Integer.parseInt(): three steps. Scanner folds all three into nextInt(). Under the hood, Scanner maintains a buffer and a delimiter pattern (whitespace by default). It splits the input into tokens at each delimiter and provides next(), nextLine(), nextInt(), nextDouble(), nextBoolean(), and others.

Wrap System.in in a Scanner to read from the keyboard (or redirected stdin). A single Scanner instance should be used for the entire program's stdin session, creating multiple Scanner instances on System.in is a common mistake that causes buffered bytes to be lost.

Java
import java.util.Scanner;

Scanner sc = new Scanner(System.in);

System.out.print("Enter your name: ");
String name = sc.nextLine();           // reads a full line including spaces

System.out.print("Enter your age: ");
int age = sc.nextInt();                // reads and parses the next integer token

System.out.print("Enter your GPA: ");
double gpa = sc.nextDouble();          // reads and parses the next double token

System.out.printf("Hello %s, age %d, GPA %.2f%n", name, age, gpa);

sc.close(); // close when done — releases the underlying stream
Do not close a Scanner wrapping System.in if you plan to read more input later, closing it closes System.in permanently for the JVM process. In short programs it is fine; in long-running apps, keep the scanner open for the lifetime of the program.

The nextLine() After nextInt() Pitfall

This is the most common Scanner mistake. When you call nextInt() (or nextDouble(), next()), it reads the integer token but leaves the newline character (\n) sitting in the buffer. The very next nextLine() call immediately consumes that leftover newline and returns an empty string: it does not wait for the user to type anything.

The fix is straightforward: add a sc.nextLine() call immediately after each typed-value method to consume the dangling newline before reading the next full line.

Java
Scanner sc = new Scanner(System.in);

System.out.print("Enter quantity: ");
int qty = sc.nextInt();
sc.nextLine(); // ← consume the leftover '\n' from the Enter key

System.out.print("Enter product name: ");
String product = sc.nextLine(); // now correctly waits for user input

System.out.println(qty + " × " + product);

// Alternative: read everything as lines and parse manually
Scanner sc2 = new Scanner(System.in);
System.out.print("Enter quantity: ");
int qty2 = Integer.parseInt(sc2.nextLine().trim());
System.out.print("Enter product name: ");
String product2 = sc2.nextLine();
// No leftover-newline problem because nextLine() always consumes the full line

Note: The "read everything as lines, then parse" pattern avoids the leftover-newline problem entirely and makes error handling easier: you can validate the trimmed string before parsing rather than letting InputMismatchException bubble up.

Scanning Files and Strings

Scanner is not limited to stdin. It accepts a File, a Path, a String, or any InputStream as its source. Scanning a file is useful for quick-and-dirty token extraction from small files. For large files you should prefer Files.lines() or BufferedReader (see note below), because Scanner is not buffered by default when wrapping an InputStream and can be slow on large input.

Scanning a String is handy in unit tests or when parsing an in-memory response body token by token without splitting manually.

Java
import java.io.File;
import java.nio.file.Path;
import java.nio.charset.StandardCharsets;

// Scan a file — throws FileNotFoundException (checked)
try (Scanner fileScanner = new Scanner(
        new File("/data/numbers.txt"), StandardCharsets.UTF_8)) {
    while (fileScanner.hasNextInt()) {
        int n = fileScanner.nextInt();
        System.out.println(n);
    }
}

// Scan a Path (Java 10+)
try (Scanner pathScanner = new Scanner(
        Path.of("/data/records.csv"), StandardCharsets.UTF_8)) {
    pathScanner.useDelimiter(",|\r?\n"); // split on comma or newline
    while (pathScanner.hasNext()) {
        System.out.println(pathScanner.next().trim());
    }
}

// Scan an in-memory String
String csv = "10,20,30,40";
Scanner strScanner = new Scanner(csv).useDelimiter(",");
int sum = 0;
while (strScanner.hasNextInt()) {
    sum += strScanner.nextInt();
}
System.out.println("Sum: " + sum); // 100
For reading large text files line by line, prefer Files.lines(path) (returns a lazy Stream<String>) or new BufferedReader(new FileReader(file)). Both use an internal buffer and are significantly faster than Scanner on multi-megabyte files.

Console: Terminal I/O and Password Input

java.io.Console, available since Java 6, represents the physical console attached to the JVM process. It has two capabilities that Scanner cannot replicate: reading a password with readPassword(), which disables terminal echo so the typed characters do not appear on screen, and writing directly to the terminal even when stdout is redirected to a file.

The console is obtained via System.console(), which returns null when there is no attached terminal, for example when the program is run from an IDE, a CI script, or any context where stdin/stdout are piped. Always null-check before using it.

Java
import java.io.Console;

Console console = System.console();

if (console == null) {
    System.err.println("No console available — run from a terminal");
    System.exit(1);
}

// Read a line with a prompt
String username = console.readLine("Username: ");

// Read a password — characters are NOT echoed to the screen
char[] password = console.readPassword("Password: ");

System.out.println("Logged in as: " + username);

// Process the password as a char[] — clears it from memory when done
// (unlike String, a char[] can be zeroed out explicitly)
boolean valid = authenticate(username, password);
java.util.Arrays.fill(password, '\0'); // zero out after use

// Formatted output directly to the terminal (bypasses any stdout redirect)
console.printf("Welcome, %s!%n", username);
Never store a password in a String. Strings are immutable and interned: their contents linger in the Java heap (and potentially in heap dumps) until GC collects them. readPassword() returns a char[] specifically so you can zero it out with Arrays.fill(password, '\0') immediately after use.

Scanner vs BufferedReader: When to Use Each

Both Scanner and BufferedReader read text input, but they are designed for different tasks. Scanner is a tokeniser: its job is to break input into typed tokens. BufferedReader is a line reader: its job is to read text efficiently, one line at a time, with as little overhead as possible.

Choose Scanner when you need typed parsing (integers, doubles, custom patterns), interactive prompting with typed values, or quick in-memory string parsing. Choose BufferedReader when reading large files where performance matters, when you only need lines and will parse them yourself, or when you need the Stream<String> interface via lines().

FeatureScannerBufferedReader
Typed parsing (nextInt, nextDouble)✅ Built-in❌ Manual
Reading full lines✅ nextLine()✅ readLine()
Stream<String> of lines❌✅ lines()
Custom delimiter / regex tokenisation✅ useDelimiter()❌
Performance on large filesSlower (unbuffered by default)Fast (buffered)
Echo-free password input❌❌ (use Console)
Sources supportedInputStream, File, Path, String, ReadableReader (wrap FileReader, InputStreamReader)