How to Get User Input in Java

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 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 alongisde 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));
}

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 alongisde 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.

Author image
Belgrade, Serbia