Introduction
Reading user input is the first step towards writing useful Java software. User input can come in many forms - mouse and keyboard interactions, a network request, command-line arguments, files that are updated with data relevant for a program's execution, etc.
We're going to focus on keyboard input via something called the standard input stream. You may recognize it as Java's System.in
.
We're going to use the Scanner
class to make our interaction with the underlying stream easier. Since Scanner
has some downfalls, we'll also be using the BufferedReader
and InputStreamReader
classes to process the System.in
stream.
In the end, we'll Decorate the InputStream
class and implement our own custom UncloseableInputStream
to handle issues with the Scanner
class.
Java Scanner Class
The java.util.Scanner
class is a simple scanner that can parse and handle primitive inputs, Strings and streams. Since System.in
is just an InputStream
, we can construct a Scanner
as such:
Scanner sc = new Scanner(System.in);
This Scanner
instance can now scan and parse booleans, integers, floats, bytes and Strings.
Let's see how we can extract information from a Scanner
into variables we can work with:
Scanner sc = new Scanner(System.in);
// Read an integer into a variable
int myInteger = sc.nextInt();
// Read a byte into a variable
byte myByte = sc.nextByte();
// Read a line until newline or EOF into a string
String myLine = sc.nextLine();
// Closing the scanner
sc.close();
Again, the constructor doesn't have to take System.in
. It can take any File
, InputStream
, Readable
, ReadableByteChannel
, Path
(of a file to read), or even String
. Additionally, as the second argument, it can specify a character encoding to interpret said characters by:
Scanner sc = new Scanner(new FileInputStream("myFile.txt"), "UTF-8");
Note that Scanner has to be closed when you're done working with it. The easiest way to do this is via the try-with-resources statement.
Scanner Methods for Reading
Available methods for reading the next token using the scanner method are:
Method | Return type | Description |
next() | String | Finds and returns the next complete token from the scanner. |
nextByte() | byte | Scans the next token of the input as a byte. |
nextDouble() | double | Scans the next token of the input as a double. |
nextFloat() | float | Scans the next token of the input as a float. |
nextInt() | int | Scans the next token of the input as an int. |
nextLong() | long | Scans the next token of the input as a long. |
nextShort() | short | Scans the next token of the input as a short. |
nextBoolean() | boolean | Scans the next token of the input into a boolean value and returns that value. |
nextLine() | String | Advances this scanner past the current line and returns the input that was skipped. |
A method worth noting is the hasNext()
method - a generic method that will return true
if there's any type of token to be read. There are type-specific methods such as hasNextInt()
, hasNextFloat()
, hasNextLine()
etc. which you can use in the same fashion.
Problems When Using System.in With Scanner
A big problem with System.in
is that it's an InputStream
. When working with it, the Scanner
will always expect more input until the InputStream
is closed. Once the stream is closed, we can no longer access the input from the Scanner
.
Besides closing itself, the Scanner
class will also close the InputStream
if it implements Closeable
.
Since InputStream
does, that means that the Scanner
will close the System.in
stream to the entirety of your program.
That being said, if you close a Scanner
and thus the System.in
as well, you can't use System.in
again:
Scanner sc = new Scanner(System.in);
System.out.println(sc.nextInt());
sc.close();
System.out.println("Closing the scanner...");
sc = new Scanner(System.in);
System.out.println(sc.nextInt());
sc.close();
System.out.println("Closing the scanner...");
This results in:
1
1
Closing the scanner...
Exception in thread "main" java.util.NoSuchElementException
at java.util.Scanner.throwFor(Scanner.java:862)
at java.util.Scanner.next(Scanner.java:1485)
at java.util.Scanner.nextInt(Scanner.java:2117)
at java.util.Scanner.nextInt(Scanner.java:2076)
at com.company.Main.main(Main.java:18)
This makes work with Scanner
and System.in
much more complicated. We'll be fixing this in the final section.
BufferedReader and InputStreamReader
Instead of a Scanner
, you can also use a BufferedReader
alongside an InputStreamReader
to get the user input:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while((line = br.readLine()) != null){
System.out.println(String.format("The input is: %s", line));
}
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
Here, we just repeat back the input String with a prefix:
Hello!
The input is: Hello!
I'd like to order some extra large fries.
The input is: I'd like to order some extra large fries.
^D
Process finished with exit code 0
The BufferedReader
is well-suited for reading Strings, but doesn't have built-in methods for handling numbers. To read an integer, you'd have to parse it from a String:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int a = Integer.parseInt(br.readLine());
System.out.println(a);
This works just fine now:
5
5
Custom Uncloseable InputStream
Fortunately, there's a solution to Scanner
closing the System.in
stream thanks to the Decorator Design Pattern. We can implement our own InputStream
and just make the close()
method do nothing so that when Scanner
calls it, it won't affect the underlying standard input:
public class UnclosableInputStreamDecorator extends InputStream {
private final InputStream inputStream;
public UnclosableInputStreamDecorator(InputStream inputStream) {
this.inputStream = inputStream;
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public int read(byte[] b) throws IOException {
return inputStream.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return inputStream.read(b, off, len);
}
@Override
public long skip(long n) throws IOException {
return inputStream.skip(n);
}
@Override
public int available() throws IOException {
return inputStream.available();
}
@Override
public synchronized void mark(int readlimit) {
inputStream.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
inputStream.reset();
}
@Override
public boolean markSupported() {
return inputStream.markSupported();
}
@Override
public void close() throws IOException {
// Do nothing
}
}
When we modify our troublesome code to use the custom InputStream
, it will execute without issues:
public class ScannerDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(new UnclosableInputStreamDecorator(System.in));
System.out.println(sc.nextInt());
sc.close();
System.out.println("Closing the scanner...");
sc = new Scanner(new UnclosableInputStreamDecorator(System.in));
System.out.println(sc.nextInt());
sc.close();
System.out.println("Closing the scanner...");
}
}
Running this will result in:
1
1
Closing the scanner...
1
1
Closing the scanner...
Conclusion
In this article, we've covered how to use the Scanner
class to read user input. We've then used the BufferedReader
class alongside the InputStreamReader
as an alternative approach.
Finally, we've implemented our own InputStream
to avoid the issue of Scanner
closing the System.in
stream for the whole program.
Hopefully, you've learned how to handle basic console input in Java and some common errors that you might encounter along the way.