[Java-201a] The Hidden Dangers of Scanner

Jack Boyuan Xu
The Startup
Published in
4 min readJan 17, 2020

We use Scanner in Java to obtain user input. However, not realizing the hidden dangers of Scanner can lead to hours of frustration. Frustrate no more! Because now I will tell you all about it.

Don’t Close Scanner!

When we use Scanner and leave it open, our IDE may complain that we have a resource leak. Even if the IDE does not complain, usually it’s a good idea to close something if we don’t need it anymore. The intuition, then, is to simply close it… Right? Consider the following code snippet:

public class Main {

public static void getInput1() {
Scanner scanner = new Scanner(System.in);
scanner.nextLine(); // Ask for user input
scanner.close();
}

public static void getInput2() {
Scanner scanner = new Scanner(System.in);
scanner.nextLine(); // Ask for user input
scanner.close();
}

public static void main(String[] args) {
getInput1();
getInput2();
}
}

At a glance, there is nothing wrong with the code. We create a new Scanner, close it, create another one, and close that one. However, if we try to run it, this is what we get:

ss
Exception in thread "main" java.util.NoSuchElementException: No line found
at java.util.Scanner.nextLine(Scanner.java:1540)
at com.usc.csci201x.Main.getInput2(Main.java:17)
at com.usc.csci201x.Main.main(Main.java:23)
Process finished with exit code 1

I typed ss.

What’s going on here then? Observe the constructor of Scanner when we instantiated it: we gave it System.in . That’s an okay thing to do. However, it has an implication that may not be obvious: when we do scanner.close() , we are closing System.in as well.

What is System.in?

System.in is the system input stream opened up by the JVM. Normally it stays open throughout the lifetime of the program so we can capture user input. However, if we close it prematurely, we will not be able to capture user input anymore. Hence, when we try to read input with System.in again, it tells us No line found .

The Solution

Just don’t close Scanner.

If you must close Scanner, you can wrap System.in inside of a FilterInputStream and override the close() method:

Scanner scanner = new Scanner(new FilterInputStream(System.in) {
@Override
public void close() throws IOException {
//don't close System.in!
}
});

And now it is safe to call scanner.close() as it will only close FilterInputStream instead of System.in .

If your application is single-threaded, consider making Scanner static. Maybe like so:

public class GlobalScanner {

private static Scanner scanner = null;

public static Scanner getScanner() {
if (scanner == null) {
scanner = new Scanner(new FilterInputStream(System.in) {
@Override
public void close() throws IOException {
//don't close System.in!
}
});
}
return scanner;
}
}

Always Use nextLine()!

Normally when we try to get user input, we use String input = scanner.nextLine(); . However, as we saw, nextLine() returns a String. There are times when we only want to get, say, an integer. Scanner provides nextInt() to strictly return an integer. Consider the following code snippet:

public class Main {

public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
System.out.println("a = " + a);
int b = scanner.nextInt();
System.out.println("b = " + b);
}
}

This piece of code works completely as expected. We are being prompted twice and our integer inputs are printed. The output looks something like this:

1
a = 1
2
b = 2
Process finished with exit code 0

However, if we change the second nextInt() to nextLine() :

public class Main {

public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
System.out.println("a = " + a);
String b = scanner.nextLine();
System.out.println("b = " + b);
}
}

This new piece of code certain looks okay, but if we run it:

1
a = 1
b =
Process finished with exit code 0

Woah! The program ended before I got a chance to type anything for b. Why is that?

The Elusive \n

\n is commonly known as a new line character. It is a character representation of the Enter key on our keyboard. When we type things into the console, we are actually typing into a buffer of sorts. The end of the line is automatically appended with a \n the moment we hit the Enter key.

This is all fine if we are using nextLine(), as it consumes everything in the buffer. However, when we mix nextInt() and nextLine(), and since nextInt()strictly takes in integers, \n will not be consumed since it isn’t an integer and the next nextLine() call will see the \n character in the buffer and think the user has already pressed Enter, when in reality the user has not. As a result, nextLine() is instantly executed, giving the user no chance to enter anything.

The Solution

Always use nextLine() and do the parsing manually.

Closing the Scanner prematurely and mixing nextLine() with other nextXXX() are the two most common issues that greatly frustrate students. But you won’t fall into those traps anymore now that you’ve read this article, right?

--

--

Jack Boyuan Xu
The Startup

Co-founder & Tech Lead @ EthSign. Blockchain Lecturer @ USC.