Search code examples
javajava.util.scannersystem.in

Java System.in hasNext method works only first time it is called


This problem has me stumped. I've written a method which is called from inside a loop. The first time it works perfectly, after that it just hangs.

This is the method:

public static String promptUser(){
    String path = "";
    Scanner reader = new Scanner(System.in);  

    while (!path.contains(ROOT_FOLDER)){
        System.out.println(String.format("Please paste in the directory path from WinSCP, including %s: ", ROOT_FOLDER));

        while (true) {
            if (reader.hasNext()){
                path = reader.nextLine();
                break;
            }
        }
    }
    reader.close();
    return path;
}

And this is the loop

        while (true) {
        try {
            listFiles(promptUser());
            break;
        } 
        catch (Exception e) {
            e.printStackTrace();
            System.err.println(e.getMessage());
        }
    }

The first time I run it, it will prompt the user as many times as it takes to get the info we need (a directory path). It then sends a request for that to an FTP server and if that path does not exist I want it to continue prompting the user. But my debugger is telling me on the second go round it just hangs on:

        if (reader.hasNext()){ 

No amount of hitting the enter key gets it to continue. On the first invocation of promptUser I could enter something without the root folder and it would just keep asking until it got the root folder. So why doesn't it do that on the second invocation?

What is going on?


Solution

  • The first time you call promptUser it does the following:

    1. Create a Scanner that wraps System.in.
    2. Call hashNext which blocks until there is data to be read.
    3. Read a single line.
    4. Close the Scanner.
    5. Return the line.

    The problem is step 4. When you close the Scanner, you also close System.in.

    Next time you call promptUser

    1. Create another Scanner that wraps System.in. System.in is closed at this point.
    2. Call hashNext() which immediately returns false ... because the stream wrapped by the Scanner is closed.
    3. Repeat 2. ad nauseam. You have an infinite loop.

    Solution: don't close System.in or a Scanner that wraps it. And don't just create a second Scanner that wraps System.in. Instead, reuse the original Scanner.


    But is it necessary to close the scanner at some point?

    You should never need to close a Scanner that wraps (the original) System.in stream. The reasons that we close streams are:

    • to cause buffered data to be written (for an output stream), and
    • to avoid file descriptor leaks.

    The problem with leaking file descriptors is that a process (i.e. your JVM) can only have a certain number of files open at a time. If you leak file descriptors, you may find that attempts to open files start to fail with unexpected IOExceptions.

    In the case of System.in there is no data to be written. Since you can't open System.in again (because you don't know what it is connected to) at most one file descriptor can be "leaked", so this is not a problem.

    Eclipse warns that I should.

    The Eclipse heuristics about closing scanners, etcetera are rather simplistic. In this case, the warning is probably a false alarm, though I would need to see the code to be sure.