AlgoMaster Logo

Scanner Class

Medium Priority23 min readUpdated September 21, 2026
Listen to this chapter
Unlock Audio

java.util.Scanner is a token-based parser that reads text from a file, an input stream, a string, or the keyboard and hands back ready-to-use primitive values and strings. It was added in Java 5 and is the easiest way to pull structured input out of a small text file without writing any parsing logic. This lesson covers how Scanner views its input, how to construct one from common sources, the token and line methods, the newline trap, custom delimiters, locale-sensitive number parsing, and when to use a different tool.

How Scanner Sees Its Input

A Scanner does not read the input as a sequence of bytes or characters. It reads it as a sequence of tokens separated by delimiters. By default, the delimiter is whitespace (one or more spaces, tabs, or newlines), so any chunk of non-whitespace text between two whitespace runs is a single token.

Consider a small orders.txt that contains three values:

To a Scanner with the default delimiter, the input above is a stream of three tokens: "1001", "Notebook", and "4.99". The spaces are gone, swallowed by the delimiter rule. You ask the Scanner for the next token in the shape you want, and it parses for you.

The Scanner walks left to right, never backwards. Every time you call a next... method, you consume the next token and the cursor moves forward. There is no "rewind", and there is no random access. If you need to look at token 7 you have to walk past the first six.

Two parallel families of methods let you pull tokens out:

  • The nextXxx() family consumes the next token and parses it: next(), nextInt(), nextDouble(), nextLong(), nextBoolean(). If the next token can't be parsed as the requested type, they throw InputMismatchException.
  • The hasNextXxx() family peeks: it tells you whether a next token exists and, for typed variants, whether it parses cleanly. hasNext() checks for any token, hasNextInt() checks the next token parses as an int, and so on. These never advance the cursor.

The combination of "peek with hasNext..., consume with next..." is the standard pattern. You loop while a token is available, you peek at its shape, and you call the right typed method to grab it.

The Scanner pulled three tokens off the input, parsed them as three different types, and the cursor finished sitting at the end of the input. The try-with-resources block closes the Scanner at the end, which matters even on a String source so the Scanner releases internal state.

Constructing a Scanner

Scanner has overloaded constructors so you can point it at almost any text source. The common ones for file work and beginner code are:

ConstructorSourceTypical use
new Scanner(File source)A java.io.FileReading a local file by path
new Scanner(Path source)A java.nio.file.PathSame as File, but with the modern path API
new Scanner(InputStream source)Any byte streamReading System.in, network streams, classpath resources
new Scanner(String source)A string in memoryParsing tokens out of a string you already have
new Scanner(Readable source)Anything implementing Readable (e.g., BufferedReader)Wrapping an existing character source

The File, Path, and InputStream constructors that read bytes need to know an encoding. Most overloads accept an extra Charset (or charset name) argument. If you don't pass one, Scanner uses the platform's default charset, which is fine for ASCII files on your own machine but error-prone for shared code. Always pass StandardCharsets.UTF_8 when you mean UTF-8.

Reading a file with a Path and an explicit charset looks like this. Consider an inventory.txt shipped next to your program:

The loop reads two tokens per iteration: a string SKU and an integer quantity. The whitespace between them, including the newlines, is the delimiter. The Scanner doesn't care that the data spans three lines; from its point of view it's a flat stream of six tokens.

Three details about this example:

  • The Scanner is built directly from a Path. There's no need for an intermediate FileReader or BufferedReader for this case. Scanner handles the file plumbing internally.
  • The charset is explicit. UTF-8 is the safe default for new code, and being explicit means the same program produces the same result on a teammate's machine.
  • The throws IOException declaration is required because constructing a Scanner from a file can fail (file missing, permissions, and so on). The compiler enforces this.

A Scanner built from a String never throws IOException, which is sometimes a convenience when you want to test parsing logic without a real file:

Same parsing code, different source. That symmetry between file, stream, and string sources is what makes Scanner useful for small programs.

Opening a Scanner on a file does not load the whole file into memory. It reads from the underlying stream as you pull tokens, so memory use stays small even for large files. The performance problem with Scanner on large files is CPU, not memory.

Token Methods: next, nextInt, nextDouble, and Friends

Scanner exposes a typed next method for each commonly read primitive, plus a generic next() for raw strings. Each typed method has a matching hasNext... peek.

Read methodReturnsThrows if next token doesn't parse
next()StringNever; just returns the token
nextInt()intInputMismatchException
nextLong()longInputMismatchException
nextDouble()doubleInputMismatchException
nextFloat()floatInputMismatchException
nextBoolean()booleanInputMismatchException
nextByte()byteInputMismatchException
nextShort()shortInputMismatchException

All of them throw NoSuchElementException if there's no token at all (the stream is exhausted). Their hasNext... peers return false in that case, which is how you avoid the exception.

A typical pattern for reading mixed types out of a file is to peek with the right hasNext... before calling the typed method. Consider a small wishlist.txt:

A program that totals the quantities and skips any malformed lines could look like this:

The hasNextInt() peek means a typo like SKU102 one wouldn't crash the program. The Scanner would notice that "one" isn't a valid int, fall into the else branch, consume the bad token with next(), and move on. Without the peek, nextInt() on "one" throws InputMismatchException and the loop dies.

next() is the escape hatch when you need a raw token without parsing. It's also how you skip over a token without caring what it is.

One detail about how Scanner reads numbers: by default it follows the system's locale rules. On a machine set to a European locale, nextDouble() expects 3,14 (with a comma), not 3.14. The locale section below covers the fix.

nextLine and the Newline Trap

nextLine() is different from every other read method on Scanner. It does not pull a token. It pulls everything from the cursor up to and including the next line terminator, and returns the text before the terminator as a String. The terminator itself is consumed but not returned.

That distinction is fine on its own. The trap appears when you mix nextLine() with the typed token methods like nextInt(). The canonical example:

The first item came back as an empty string, and "Eraser" was never read. The bug is mechanical. After nextInt() consumes "3", the cursor is sitting right after the 3 and before the \n that ends that line. The integer token didn't include the newline. The first nextLine() call then reads "from the cursor to the next newline", which is the empty string between the 3 and the \n. The cursor then jumps past that newline and the next nextLine() reads "Pen" as expected.

The state machine makes the trap concrete. The orange state right after nextInt() is the leftover-newline state. Every typed next... method leaves the cursor in that state if the token ended at a line break, and the next nextLine() call consumes the leftover newline as if it were an empty line.

There are two clean fixes.

Fix 1: Eat the leftover newline with a throwaway `nextLine()`.

The extra scanner.nextLine() after nextInt() consumes the empty remainder of the first line and the trailing newline. After that the cursor sits at the start of "Pen" and the loop behaves.

Fix 2: Avoid mixing the two families. Use `next()` if the next thing is a single token.

next() ignores leading whitespace including newlines, so it doesn't care where the previous token ended. This works as long as each item is a single token (no spaces inside it). If items can contain spaces (e.g., "Mechanical Pencil"), use nextLine() and pay the consume-the-newline tax.

A useful rule of thumb: when there is a count on one line followed by lines of arbitrary text, add one throwaway nextLine() after the count.

Custom Delimiters

The default delimiter pattern is \\p{javaWhitespace}+, which is one-or-more whitespace characters. Swap it out by calling useDelimiter(String pattern) or useDelimiter(Pattern pattern). The argument is a regular expression, so anything that the java.util.regex engine accepts is fair game. A small handful of patterns come up for file work.

The most common case is a CSV-ish file where fields are separated by commas. Consider an orders.csv:

If you keep the default delimiter, Scanner sees three tokens (one per line, with embedded commas). That's not useful. Switching the delimiter to a comma fixes it for fields on the same line, but then a newline at the end of one line glues onto the start of the next token. A pattern that splits on either a comma or a newline solves the whole problem:

A couple of things are happening in that pattern. The , matches a literal comma. The | is regex alternation, meaning "or". And \\R is the regex shortcut for any line-break sequence, which covers \n, \r, and \r\n in one symbol. The double backslash is there because Java string literals need \\ to produce a single backslash in the actual string.

The delimiter consumes both commas and line breaks, and the Scanner yields a flat stream of six tokens per row pair. The parsing code on the consumer side doesn't change between a space-separated and a comma-separated file. Only the delimiter does.

Scanner builds and uses a regex Pattern internally for delimiters. Every token boundary triggers a regex match, which is part of why Scanner is slower than character-oriented readers like BufferedReader. For small files this cost is invisible. For files with millions of fields, it adds up.

A simpler case: a single column of prices, one per line, to total. The delimiter can just be \\R, the line-break shortcut, so each line is one token:

By making the newline the delimiter, each line is a single token. hasNextDouble() peeks at that token, parses it, and the loop accumulates the total. There is no nextLine() in this code, so there's no newline trap to worry about.

One more pattern that comes up: delimiters with surrounding whitespace. A file written by hand might have spaces around its commas ("1001 , Notebook , 4.99"). The pattern \\s*,\\s*|\\R handles that by allowing optional whitespace on either side of the comma.

You can change the delimiter at any point, and the change applies to the next token. This means you can use one delimiter for the header line and a different one for the body, by switching mid-file. That kind of trick is fragile and usually a sign that a real parser is needed, but it's possible.

Locale-Sensitive Number Parsing

Scanner's number methods respect the JVM's default locale. In an English locale, 1,234.56 is one thousand two hundred thirty-four point five six, and the comma is a grouping separator. In a French locale, 1 234,56 means the same thing, but with a space for grouping and a comma for the decimal point. If you run your program on a machine set to a different locale than the file was written in, nextDouble() either parses a different value or throws InputMismatchException.

The fix is useLocale(Locale locale). Set it explicitly to match how the file's numbers are formatted, regardless of where the program runs.

Two different formats, two different locale settings, same numeric result. The Locale.GERMANY setting tells the Scanner that , is a decimal point in this context, so "1.234,56" parses as one thousand two hundred thirty-four point five six.

For files that you control, the cleanest move is to pick one locale (usually Locale.US for ASCII-friendly dot-as-decimal numbers) and lock it in. This protects you from a teammate's machine being configured differently:

Locking the locale is a one-line change with no runtime cost, and it removes a class of bugs that only surfaces when someone runs the program in a different country. Do it every time you parse numbers from a file.

Setting the locale is free; parsing doesn't get slower. The reason to use a fixed locale is correctness, not performance.

Closing a Scanner: Try-With-Resources

Scanner implements java.io.Closeable, so it works with try-with-resources. When the Scanner is built over a file or an InputStream, closing it closes the underlying source too. That's what you want for files: the OS handle gets released as soon as the try block exits, no matter whether the block finished normally or threw an exception.

If you forget the try-with-resources and the program throws halfway through reading the file, the file handle stays open until the JVM exits or the garbage collector eventually runs. On a long-running program (a server, say) that's a real resource leak. On a short script the cost is small, but the habit is worth forming.

One situation where closing matters in a less obvious way: new Scanner(System.in). Closing that Scanner also closes System.in, which is usually not what you want, because nothing else in the program will be able to read input afterwards. For console programs that keep running after a Scanner usage, leave that particular Scanner open or use a fresh one each time.

What's wrong with this code?

The Scanner is never closed. If hasNext() or next() throws (say, because a malformed token causes InputMismatchException further down the road), the file handle leaks. Even without an exception, relying on the JVM to clean up the resource on exit is fragile.

Fix:

The try-with-resources block guarantees close() runs whether the loop exits normally or via an exception.

Reading From System.in

System.in is an InputStream connected to the keyboard (or whatever else is piping input to your program). Wrap it with a Scanner to read user input.

Sample interaction:

The same token model and the same newline trap apply here. If you ask for an int and then a product name on the next line, you need the throwaway nextLine() after the nextInt(). The Scanner doesn't care that the input is coming from a person rather than a file.

This example deliberately doesn't use try-with-resources. Closing a Scanner(System.in) also closes System.in, which closes the door on any future console input from anywhere in the program. For short scripts where the Scanner outlives the whole program, leaving it open is fine.

When NOT to Use Scanner

Scanner is built for convenience, not throughput. It does a lot of work per call: it consults the delimiter pattern, runs a regex match, applies the current locale, and constructs String and number objects. For a small configuration file, none of that matters. For a 10 GB log file or a high-throughput data pipeline, it matters a great deal.

A few cases where Scanner is the wrong tool:

  • Very large files. BufferedReader.readLine() plus String.split() is typically 5x to 10x faster than Scanner for the same parsing job. The exact ratio depends on the workload, but the gap is real and grows with file size. Use BufferedReader when speed matters.
  • Strict CSV with quoted fields. A real CSV file can have quoted fields with commas inside them ("Smith, Jane",1001,4.99), escaped quotes inside quoted fields, and multi-line records. A delimiter pattern like ,|\\R won't handle any of those. Use OpenCSV or Apache Commons CSV for that.
  • High-throughput log scanning. When processing logs at megabytes per second, Scanner's per-token regex and locale work will dominate the CPU. BufferedReader or the newer Files.lines is more appropriate. Scanner is fine for picking apart one log entry once you've isolated it.
  • Binary data. Scanner is text-only. For binary files (images, compressed archives, custom binary formats) use DataInputStream or FileChannel.

As a rough rule of thumb, Scanner reads on the order of 100K to 500K tokens per second on modern hardware. BufferedReader.readLine() reads tens of millions of lines per second. The convenience of Scanner has a measurable per-call cost.

Scanner is well-suited to small files, informal formats, and code where the parsing logic is the boring part. That covers a large chunk of beginner and intermediate work, which is why it's the right starting tool even though it's not the fastest one.

Quiz

Scanner Class Quiz

10 quizzes