Java Scanner — The Silent Skip That Lost Payment Data
After nextInt() in batch, nextLine() returns empty strings - silent data loss.
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
- Scanner wraps System.in or File to read text input, converting tokens to Java types.
- Key methods: nextLine(), nextInt(), nextDouble() — each parses a specific type.
- hasNextInt() and similar let you validate before reading — prevents InputMismatchException crashes.
- Performance: Scanner is slower than BufferedReader for large files due to regex parsing overhead.
- Production trap: nextLine() after nextInt() silently reads leftover newline — always add an extra nextLine().
- Biggest mistake: Multiple Scanner objects on System.in — closing one closes the stream for all.
Imagine a cashier at a supermarket. Your program is the cashier, and Scanner is the little device that reads the barcode when you slide an item across it. Without it, the cashier has no way to know what item just arrived. Scanner is Java's built-in 'reading device' — it picks up text typed on a keyboard (or written in a file) and hands it to your program so your code can actually do something with it. No Scanner, no input. It's that simple.
Every useful program in the real world needs to talk to a human. A login screen needs a username. A calculator needs two numbers. A quiz app needs an answer. Without the ability to read what a user types, your Java program is essentially a locked room — it can think, but it can't listen. That's where the Scanner class steps in, and it's one of the very first things every Java developer learns for exactly that reason.
Before Scanner existed, reading input from a keyboard in Java was genuinely painful. You had to work with low-level streams, wrap them in readers, and handle checked exceptions just to read a single number. Scanner wraps all of that complexity in a clean, beginner-friendly API. You get methods like nextLine(), nextInt(), and nextDouble() that read exactly the type of data you want — no ceremony required.
By the end of this article you'll know how to set up a Scanner, read strings and numbers from the keyboard, read input from a file, close the Scanner correctly, and — critically — avoid the two nasty bugs that trip up almost every beginner. You'll also have three fully runnable programs you can copy, run, and tweak right now.
What Is Scanner and How Do You Set It Up?
Scanner lives in the java.util package, which means you need to import it before you can use it. Think of an import like telling your kitchen which cookbook to pull off the shelf — you're telling Java 'I need the Scanner recipe from the java.util cookbook'.
Once imported, you create a Scanner object. An object is just a working instance of a class — like printing a specific form from a template. You hand the Scanner constructor a source to read from. The most common source is System.in, which represents the keyboard. Literally: System.in means 'standard input', which is the stream of characters that flows in when a user types.
The Scanner then sits and waits. When your code calls a method like nextLine(), the program pauses, a cursor blinks in the terminal, the user types something and hits Enter, and Scanner captures everything typed before that Enter key.
One important habit to build immediately: always close your Scanner when you're done with it. A Scanner holds a connection to its source (keyboard or file). Leaving it open is like leaving a tap running — it wastes resources and can cause subtle bugs in larger programs.
System.out.print().Reading Different Data Types — Strings, Integers and Decimals
Scanner doesn't just read raw text. It can parse that text directly into the Java data type you need — int, double, boolean, and more. Each data type has its own dedicated method, and choosing the right one saves you from having to convert strings manually.
nextLine() reads an entire line of text up to (but not including) the newline character created by pressing Enter. It returns a String.
next() reads a single 'token' — one word, stopping at whitespace. Useful for reading one word at a time.
nextInt() reads the next token and tries to parse it as a whole number (int). If the user types '42', you get the integer 42, ready for arithmetic.
nextDouble() does the same for decimal numbers. The user types '3.14' and you get a double you can multiply, divide, or compare.
The program below builds a simple age-and-height form. Notice how we mix data types in one conversation — that's exactly what real programs do.
next() call if you plan to use nextLine() afterwards.next() reads one word; nextInt() reads an integer.Reading Input From a File — Same Tool, Different Source
Here's something that surprises many beginners: Scanner isn't just for keyboards. Because it accepts any source that Java can read from, you can point it at a text file and it reads line by line (or token by token) in exactly the same way. This is genuinely powerful — learn Scanner once, use it two ways.
To read from a file, you create a File object pointing at the file path, then pass that File object to the Scanner constructor instead of System.in. The rest of the API — nextLine(), nextInt(), hasNextLine() — works identically.
hasNextLine() is your best friend when reading files. It returns true as long as there are more lines to read, which makes it a perfect condition for a while loop. Without it you'd have to know in advance how many lines the file has.
Note that opening a file can fail (file not found, permission denied), so Java forces you to handle a FileNotFoundException. We use a try-with-resources block below, which is the modern, correct approach — it automatically closes the Scanner when the block ends, even if an exception occurs.
close() in a finally block, and it's the pattern you'll see in all modern Java codebases.Validating Input in a Loop — Building a Bulletproof Input Reader
Real programs can't trust users to type perfectly. Someone will type 'abc' when you asked for a number. If nextInt() gets a non-numeric token it throws an InputMismatchException and crashes your program. That's a terrible user experience.
The professional solution is to validate input inside a loop. Scanner's hasNextInt() method (and its siblings hasNextDouble(), hasNextLong()) lets you check whether the next token is actually parseable as that type — before you try to parse it. Think of it as asking 'is the next thing in the queue actually a number?' before reaching in to grab it.
The loop below keeps asking until the user provides a valid positive integer. Notice two things: we call next() to consume the bad token when validation fails (otherwise Scanner is stuck on the same bad input forever), and we give the user a clear error message so they understand what went wrong.
next(), your loop becomes an infinite error printer — a classic bug that crashes nothing but infuriates users.next() in the else branch of a hasNextXxx() check to remove the invalid token from the buffer.next() in the else branch.Custom Delimiters — Reading Comma-Separated Data Like a Pro
By default, Scanner splits input on whitespace (spaces, tabs, newlines). But you can change this behavior using the useDelimiter() method. This is incredibly useful when you need to parse structured data like CSV lines, pipe-delimited logs, or any custom-separated input.
useDelimiter() takes a regular expression (or a Pattern) that defines the new delimiter. Once set, all nextXxx() methods will split tokens based on that pattern instead of whitespace.
A common use case: reading a line from a CSV file where values are separated by commas. You can set the delimiter to "," or a more robust pattern like ",|" to handle multiple delimiters.
Be careful: after setting a custom delimiter, nextLine() behavior changes — it reads tokens up to the newline, but the delimiter may include newline characters. Usually, for CSV parsing, you read each line with nextLine() first, then create a new Scanner for that line with a comma delimiter. That's the pattern shown below.
Scanner vs BufferedReader — When to Upgrade
Scanner is great for learning and small programs, but it has performance limitations. For reading large files (hundreds of MB), Scanner's regex-based tokenization becomes slow. That's when you reach for BufferedReader.
BufferedReader reads text from a character stream efficiently by buffering chunks of data. It only returns raw strings — you have to parse numbers or split lines yourself. But that trade-off gives you speed.
- You need to parse different data types from the same stream.
- Input size is small (a few MB or user keyboard input).
- You want simple methods like nextInt() without manual parsing.
- Performance matters (big files, high-frequency reads).
- You only need to read lines as strings.
- You want more control over encoding and buffer size.
If you're in a production system reading transaction logs, choose BufferedReader. If you're writing a quick CLI utility, Scanner is fine.
Integer.parseInt() on the split tokens — still faster than Scanner.nextDouble().The Input Pipeline — Why Your First Scanner Always Breaks
You think new Scanner(System.in) is harmless. It's not — not when you mix nextLine() with nextInt(). That trailing newline after a numeric read sits in the buffer, and your next nextLine() returns an empty string. The class isn't broken. You just don't understand how it tokenizes.
Scanner treats input as a stream of tokens separated by whitespace. nextInt() consumes the integer token but leaves in the buffer. nextLine() reads until a newline — and finds that leftover character. Fix it by adding a dummy nextLine() call after any nextInt(), nextDouble(), or nextFloat(). Or better yet, only ever use nextLine() and parse manually. That's what production code does.
The hasNextXxx() methods exist precisely to check buffer boundaries before reading. Call them. Always. A NoSuchElementException from nextInt() on empty input is a sign you skipped validation.
When Input Sources Lie — Redirects, Pipes, and EOF
Your Scanner works fine when a human types at a terminal. Run it in a CI pipeline or pipe a file into it, and suddenly hasNextLine() returns false two lines early. Why? Because System.in isn't always a keyboard. When input comes from a pipe or redirect (java App < data.txt), there's no interactive pause — the stream ends when the file does.
Scanner's method also closes the underlying stream. If you close close()System.in in a utility class, every Scanner after it is dead. That's a runtime error that won't surface until your app runs for three hours. The fix: never close a Scanner wrapping System.in. Let the JVM handle it. For file-based Scanners, use try-with-resources.
Another footgun: hasNextLine() returns false when it can't read any more characters — but that might mean a network timeout, not an empty file. Wrap your reads in a timeout or count-based loop for production systems. Don't rely on EOF being clean.
4. Using Command Line Arguments
Command-line arguments are strings passed to your program when it starts, often used for configuration or file paths. Java stores them in the String[] args parameter of main. Manually parsing these with split() or loops is error-prone and tedious. The Scanner class simplifies this by treating the entire array as a single input source. You can construct a Scanner from Arrays.toString(args) or concatenate arguments into one stream. This approach lets you reuse the same validation, delimiter, and type-reading logic you already built for console or file input. For example, if your program accepts a CSV file path and a threshold number, you can combine args[0] as a file path and args[1] as a scanned integer — all with error handling. Command-line arguments are the only way to pass data without interactive prompts, making them essential for automation scripts, CI/CD pipelines, or batch processing. Always check args.length before reading to avoid ArrayIndexOutOfBoundsException.
Conclusion
The Scanner class is Java's most intuitive tool for reading input, whether from the keyboard, a file, or command-line arguments. It handles strings, numbers, and custom separators with minimal code, making it ideal for prototypes and small utilities. However, its convenience comes with trade-offs: it lacks buffering for huge files, has no direct CSV parser, and produces confusing errors when input sources redirect or pipe. For production-grade systems handling millions of lines, prefer BufferedReader or dedicated libraries. Remember the input pipeline pitfall — mixing , next()nextInt(), and nextLine() without flushing the newline ruins your data. Validate everything in a loop, set custom delimiters for structured formats, and always close your Scanner to free resources. Mastering these patterns means you can write bulletproof input code in any context. The goal isn't to memorize APIs but to understand when raw parsing suffices and when you need a heavier tool. Start small, stay disciplined, and your input code will never surprise you.
The Silent Skip: How nextLine() After nextInt() Caused Data Loss in a Payment Pipeline
- The newline left by nextInt(), nextDouble(),
next(), etc. is a silent state — invisible to most code reviews. - Always insert a dummy nextLine() after any nextXxx() call that reads a token, if a nextLine() follows.
- Better yet: read everything as strings with nextLine() and parse numbers separately using
Integer.parseInt()— this eliminates the newline problem entirely and makes parsing errors easier to handle.
Integer.parseInt().scanner.next() to consume the bad token and prompt again.scanner.next() if input is invalid to remove it from the buffer.Add: inputReader.nextLine(); // discard leftover newlineIf you can change design: use Integer.parseInt(inputReader.nextLine()) for all number reading.Key takeaways
Common mistakes to avoid
4 patternsCalling nextLine() right after nextInt() returns an empty string
Forgetting to consume the bad token inside a hasNextInt() validation loop
scanner.next() to discard the invalid token and advance the Scanner position. Optionally assign it to a variable for logging.Creating multiple Scanner objects pointing to System.in
Using Scanner with useDelimiter() on System.in and then expecting normal line input
Interview Questions on This Topic
What is the difference between next() and nextLine() in Java's Scanner class, and when would you choose one over the other?
next() when you need a single word (like a command), and nextLine() when you need a full line of text (like a name or address). Be cautious: after next() or nextInt(), the newline remains in the buffer, so a subsequent nextLine() will consume it and return an empty string. Always flush with an extra nextLine() after a token-based read if you plan to use nextLine() afterwards.Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
That's Java I/O. Mark it forged?
9 min read · try the examples if you haven't