Reading what the user types: Scanner
A program that always prints the same thing gets old fast. Here's one that listens.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("Your name: ");
String name = sc.nextLine();
System.out.print("Your age: ");
int age = sc.nextInt();
System.out.println(name + ", next year you'll be " + (age + 1));
sc.close();
}
}
If you type Maya and then 18, it prints:
Your name: Maya
Your age: 18
Maya, next year you'll be 19
Note (age + 1) is in parentheses. Without them you'd get 181 — the left-to-right concatenation rule from the last lesson, showing up in real code on your very first program that reads input.
Three new pieces:
import java.util.Scanner; — Java loads a small set of core classes automatically. Scanner is not in that set, so you have to say you want it. Imports go at the very top of the file, above the class, always.
new Scanner(System.in) — build a Scanner and point it at a source. System.in means the keyboard. new means "construct one of these"; lesson 7 explains what that really does. For now: you need a Scanner object before you can ask it for anything, and this is how you get one.
sc.close() — release it when you're done. Skipping it won't break a small program, but it's a good reflex for when you're reading files instead of keyboards.
Asking for a specific type
sc.nextInt() // one whole number
sc.nextDouble() // one decimal number
sc.next() // one word — stops at whitespace
sc.nextLine() // one whole line, spaces included
This is nicer than Python's input(), which always hands back text you then have to convert. Scanner gives you the type you asked for directly.
The catch: if the user types abc and you called nextInt(), the program crashes with InputMismatchException. Scanner is strict about getting what it was promised.
The bug that gets everyone exactly once
int age = sc.nextInt();
String name = sc.nextLine(); // name is empty!
You run it, you type 18, press Enter, and the program never pauses to ask for a name. It just barrels on with name set to "".
Here's why. Picture the input as a stream of characters waiting in a buffer. When you type 18 and press Enter, the buffer holds:
1 8 \n
nextInt() grabs the digits and stops the instant it hits something that isn't a digit. It takes 18. It leaves the \n sitting there.
Now nextLine() runs. Its job is "read everything up to the next newline." It looks at the buffer, sees a newline immediately, and correctly reports that there were zero characters before it. You get an empty string. Scanner did exactly what it was told — the mismatch is between nextInt's "stop at the boundary" behavior and nextLine's "consume through the boundary" behavior.
The wrong mental model: that each next...() call reads "one thing the user typed." It doesn't. It reads characters from a buffer, and different methods disagree about whether the newline counts.
The direct fix is to eat the leftover newline:
int age = sc.nextInt();
sc.nextLine(); // swallow the leftover newline
String name = sc.nextLine(); // now this works
The fix I'd actually recommend: read everything with nextLine() and convert yourself.
int age = Integer.parseInt(sc.nextLine().trim());
String name = sc.nextLine();
Now every call consumes a full line including its newline, so there is never a leftover, and the whole class of bug disappears. It's one more method call and it buys you a rule you never have to think about again. The project in lesson 9 uses this style throughout.
Remember this bug. It doesn't raise an error. It just behaves strangely, and beginners regularly lose half an hour to it.
Converting between types
int n = Integer.parseInt("42"); // text to int
double d = Double.parseDouble("3.14"); // text to double
String s = String.valueOf(42); // number to text
String s2 = 42 + ""; // lazy version, also works
Integer.parseInt("abc") throws NumberFormatException. So does Integer.parseInt("42 ") with a trailing space — which is why .trim() shows up so often around user input. trim() removes whitespace from both ends of a string.
Formatted output
println is fine until you need control over how numbers look.
double price = 19.987;
System.out.printf("Unit price %.2f, quantity %d%n", price, 3);
Prints:
Unit price 19.99, quantity 3
printf takes a template with placeholders, then the values to drop in:
%.2f— a decimal number, exactly 2 places (it rounds, which is why 19.987 became 19.99)%d— a whole number%s— a string%n— a line break
Use %n rather than \n. Both work; %n picks the right line ending for the operating system you're on.
The placeholders and the arguments must line up in count and in order. Get it wrong and you don't get a compile error — you get a crash at runtime:
System.out.printf("%d and %d%n", 5); // MissingFormatArgumentException
System.out.printf("%d%n", "hello"); // IllegalFormatConversionException
This is one of the rare places where Java's compiler can't protect you, because the template is just a string. Count your placeholders by hand.
Ten-second check
Why does nextLine() return an empty string right after nextInt()?
Because nextInt() stopped at the newline instead of consuming it. The newline is still in the buffer, and nextLine() reads up to the first newline it finds — which is right there, with nothing in front of it. Add a throwaway sc.nextLine() in between, or read every input with nextLine() and convert.