For new students, it’s often fun to write interactive programs using Scanner in Java. Unfortunately, there are a handful of nasty pitfalls that don’t really contribute to a positive experience for those students. As a result, I’ve come with a warning: be careful with Scanner methods in Java.
The Problem Students Encounter
When I was learning Java for the first time, I never once used Scanner. In fact, I hadn’t even touched the utility until I already had two years of industry experience under my belt. But for whatever reason, the Java curriculum at my current institution uses Scanner extensively, so I figured I’d talk a bit about the pitfalls of some of Scanner's methods.
In particular, I want to talk about using Scanner to read user input from the command line. Often times, this is desirable because we want to prompt a user for some input like their name or a favorite number. Then, we do some fun calculation and dump the results out to the user. I’ll give you an example (user input is bold):
Enter your name: Jeremy
Hi, Jeremy!
In this example, we’ve prompted the user to enter their name, and we’ve spit it back to them. We can do this using the following snippet of Java code:
Scanner input = new Scanner(System.in);
System.out.print("Enter your name: ");
String name = input.nextLine();
System.out.println("Hi, " + name + "!");
That’s not too bad! But, what if we want to ask for a number? Fortunately for us, the Scanner API has a whole host of tokenizer methods like nextLine()
to grab whatever we want from the user and automatically convert it to the appropriate type. That said, be careful when using them.
The Explanation Students Desire
At this point, a lot of students will ask me:
What’s the big deal? Why should I have to call
nextLine()
and parse the value by hand when I can just call the proper method directly?Students Everywhere
And, their concern is completely valid! I’d never ask anyone to do more work than they have to, so what is the big deal?
Problems
Well, as it turns out, if you start using methods like nextLine()
without really paying attention to what you’re parsing, you’ll run into a few problems.
Perhaps the most obvious problem is typing issues. For example, let’s say we ask the user for a number, and they give us a name. Without proper error handling, our solution will surely crash. That said, this kind of problem isn’t too hard to discover or solve. After all, as soon as the user enters invalid text, the solution will crash with a helpful error.
Instead, there’s a much more nefarious problem that even seasoned programmers will agree is hard to troubleshoot, and it surfaces when we start to mix usage of the various Scanner methods. Take a look at the following example, and see if you can figure out what happens if we provide the program with the number 25 and my name.
Scanner input = new Scanner(System.in);
System.out.print("Enter your age: ");
int age = input.nextInt();
System.out.print("Enter your name: ");
String name = input.nextLine();
System.out.println("Your name is " + name + " and you are " + age);
Without digging too much into the solution, the average person would say this solution prints “Your name is Jeremy and you are 25”. And, to be honest, I don’t fault them for that at all. The logic is there, but the results aren’t. Luckily, I won’t leave you hanging!
Bug Hunting
If you’ve jumped ahead already and tested this solution yourself, you’ll have noticed that something strange happens. For starters, our output is wrong. Instead of “Your name is Jeremy and you are 25”, the output will read “Your name is and you are 25”. Right away, we’ll notice that the name is missing from the output meaning that the name variable is probably storing an empty String.
To make matters worse, when we actually run the solution, we’ll see that we’ll only ever be prompted for input once. After we enter our age, the entire script will dump to the screen as follows (user input is bold):
Enter your age: 25
Enter your name:
Your name is and you are 25
At this point, we’ll be scratching our heads wondering how a call to nextLine()
just immediately returned an empty String. Fortunately, I have the answer!
Escape Sequences
Whenever we prompt a user for input, they get this fancy line where they can dump some text and hit enter. Keep an eye on that last part because it’s crucial.
That text is composed of characters which all have an underlying numeric value. For instance, the letter ‘a’ has the numeric value 97 while the number ‘7’ has the numeric value 55—oddly enough. Luckily, there’s no need to memorize all these values. Instead, check out an ASCII table for a list of the first 256 characters.
If we browse that table, we’ll notice there are a handful of characters that are a little odd. For instance, the entire first column of that table contains a list of escape sequences. These characters serve a special purpose that isn’t always clearly visible on the screen.
As it turns out, there are a few of these escape sequences that are responsible for denoting line endings in text: 10 (Line Feed) and 13 (Carriage Return). Depending on the system, any combination of these two escape sequences may be used to mark the end of a line.
Rogue New Lines
For the sake of argument, we’ll assume our system marks newlines with the Line Feed character. In Java, the Line Feed character can be written using ‘\n’. In other words, every time we hit the enter key, we can imagine one of these characters sneaking its way into our text.
Now that we know about the escape sequences, why don’t we take another crack at bug hunting? Keep an eye on the line where we declare the age variable. That's where the bug begins to manifest itself.
Scanner input = new Scanner(System.in);
System.out.print("Enter your age: ");
int age = input.nextInt();
System.out.print("Enter your name: ");
String name = input.nextLine();
System.out.println("Your name is " + name + " and you are " + age);
Have you figured out where the bug might be? I’ll give you a hint. The nextInt()
method only cares to grab the next number. In other words, it leaves behind our rogue new line character (‘\n’).
After we call nextInt()
, we prompt the user to enter their name. At that point, we make a call to nextLine()
which runs up until the next new line character. Unfortunately, we left one behind when we called nextInt()
. As a result, nextLine()
doesn’t have to wait until we enter any text. It just returns an empty String.
If this is unclear, I recommend trying to feed that code snippet above a list of ages separated by spaces. For example, check out the following transcript (user input is bold):
Enter your age: 20 19 31 15
Enter your name:
Your name is 19 31 15 and you are 20
As we can see, the age is set to the first number in the list (in this case “20”), and the program never prompts for the name. Instead, the remainder of the input buffer (in this case ” 19 31 15\n″) is read in as a line and saved as the name.
In the next section, we'll talk about how to deal with the newline that is left behind.
Patch Job
Luckily, there are a couple of fixes for this problem. One relies on catching our mistake and the other relies on fixing the root cause of our problem.
Since we know we’ve left behind a new line, all we have to do is consume it. In other words, we can make an empty call to nextLine()
before calling it like normal:
Scanner input = new Scanner(System.in);
System.out.print("Enter your age: ");
int age = input.nextInt();
System.out.print("Enter your name: ");
input.nextLine();
String name = input.nextLine();
System.out.println("Your name is " + name + " and you are " + age);
Notice how we’ve added a call to nextLine()
just above the point where we store the name. Now, when we leave behind that ‘\n’ character, we’ll clean up our mess in line 5. Then, we can move on with our lives in line 6.
Alternatively, we can remove this issue altogether by replacing our nextInt()
call with a call to nextLine()
. Then, we’ll need to parse our String to get the integer we want:
Scanner input = new Scanner(System.in);
System.out.print("Enter your age: ");
int age = Integer.parseInt(input.nextLine());
System.out.print("Enter your name: ");
String name = input.nextLine();
System.out.println("Your name is " + name + " and you are " + age);
Instead of cleaning up after ourselves, we can consume the whole line in one go and parse it by hand. Personally, I prefer this option, but both work just fine. In either case, we’ve completely eliminated this nasty Scanner bug.
Open Forum
So, moral of the story is be careful with the Scanner API. While many of the methods are there for your convenience, mixing them can result in some nasty bugs that are hard to trace. Of course, it’s up to you to decide what you do with this new knowledge.
At any rate, that’s it for now! If you liked this article or you have some feedback, let me know in the comments. In addition, if you have any tips or tricks of your own for dealing with Scanner in Java, don’t hesitate to share.
And if you're feeling particularly generous, subscribe to my website, The Renegade Coder.
Top comments (2)
So, this isn't just a problem that Java has. C++ had this same issue as well. Combining cin and getline in C++ results in the same behavior.
This also isn't really a problem, if you understand that it skips delimiter, reads, then stops at the next delimiter. nextLine is really the special one in the situation because it uses it's own delimiter separate from the scanner's and reads past it's delimiter and returns everything read, but the delimiter.
Agreed! I'm aware of how these tools work, but you have to keep in mind who this piece is targeting. Beginners don't usually have the bandwidth to tackle the intricacies of parsing when they just want to get some user input.