8. More user input

Valid input

Say we wanted to get the user's age, but we wanted to make sure the input was valid. And if it wasn't, we wanted to keep prompting the user for their age until we got a valid one.

What are the ways that an age could be invalid? First off, ages should be numbers; they can't be strings. So if someone enters "a" our program should reprompt them to enter their age again.

But the nextInt method immediately fails if the input isn't an integer:

import java.util.Scanner;

class GetAge {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.println("Enter your age: ");

        // program will crash here if user enters a string
        int age = sc.nextInt();

        System.out.println("Your age is: " + age);
    }
}

If we run the following program (try it yourself, too) and type in "a", we get this output:

$ java GetAge
Enter your age: 
a
Exception in thread "main" java.util.InputMismatchException
    at java.util.Scanner.throwFor(Scanner.java:840)
    at java.util.Scanner.next(Scanner.java:1461)
    at java.util.Scanner.nextInt(Scanner.java:2091)
    at java.util.Scanner.nextInt(Scanner.java:2050)
    at GetAge.main(GetAge.java:7)

Our program crashes before it even prints the age out, because it tries to put our string input - "a" - into an int variable, giving us an InputMismatchException.

It would be nice if we could somehow test for this error, and, if it occurred, know that we needed to reprompt the user without our entire program crashing down.

Try-catch

Thankfully, there's a language construct built just for this: the try-catch.

import java.util.Scanner;
// we have to import the exception class we want to check for:
import java.util.InputMismatchException;

class GetAge {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("Enter your age: ");

        // tries to get the age as an integer
        try {
            int age = sc.nextInt();
            System.out.println("Your age is: " + age);
        } 
        // if there's an InputMismatchException thrown
        // anywhere in the above try block,
        // this catches it without crashing the program
        catch (InputMismatchException e) {
            System.out.println("You didn't input a valid age.");
        }
    }
}

The try-catch works the same as Python's try-except. The code that might fail belongs in the try block. If the code fails, we exit the try and enter the catch block.

Now if we type "a", we get this output:

$ java GetAge
Enter your age: 
a
You didn't input a valid age.

Our program doesn't crash. Hooray!

Reprompting for input

So far our program correctly recognizes when someone enters a string instead of an integer, but it doesn't reprompt them for input if they do this - it just ends. Let's add a while loop that reprompts them.

import java.util.Scanner;
import java.util.InputMismatchException;

class GetAge {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("Enter your age: ");

        // loop until we get a valid age
        while (true) {
            // check to see if we have a valid age
            try {
                // if age is invalid, will exit try and enter catch here
                int age = sc.nextInt();
                
                System.out.println("Your age is: " + age);

                // if we've gotten to this point, we've found
                // a valid age, so we can stop looping
                break;
            } 
            // age was invalid
            catch (InputMismatchException e) {
                System.out.println("You didn't input a valid age.");
                sc.next(); // clear the stream
            }
        }
    }
}

Because the condition inside our while loop is simply true, it'll loop forever, unless we get to the break. We'll only get to the break if nextInt can get the user's age without throwing any errors. Until the user enters an integer age, we'll keep asking for a correct input.

The sc.next(); statement clears the input stream. Whenever a Scanner method fails to read some input, that input is not removed from the stream. If we didn't clear the input, our program would loop forever if the user entered a string, checking and rechecking the same input to try and convert it to an int. But if we clear the stream, we can allow the user to enter some fresh input.

Our program runs like this:

$ java GetAge
Enter your age: 
apples
You didn't input a valid age.
vvvv
You didn't input a valid age.
b76
You didn't input a valid age.
66
Your age is: 66

Which is exactly what we wanted.

Negative ages

Our program only checks for strings. What happens if someone types a negative age?

$ java GetAge
Enter your age: 
-10
Your age is: -10

Uh-oh. Because -10 is a valid integer, our program treats it as a valid age.

Let's reformat our while loop so it will keep looping if the input is negative.

import java.util.Scanner;
import java.util.InputMismatchException;

class GetAge {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("Enter your age: ");

        // set invalid to true at first so we enter the loop
        boolean invalid = true;

        int age = -1;

        while (invalid) {
            try {
                age = sc.nextInt();

                if (age >= 0) {
                    invalid = false; // stop looping
                } else {
                    System.out.println("Ages can't be negative.");
                }
            } catch (InputMismatchException e) {
                System.out.println("You didn't input a valid age.");
                sc.next(); // clear the stream
            }
        }

        System.out.println("Your age is: " + age);
    }
}

Here, we've reformatted the way our while loop works. Instead of looping forever and breaking out when the input isn't a string, we loop as long as invalid is true. We only set invalid to false when the age the user enters is both an integer and non-negative. Thus, our loop will continue until we get a valid age.

Now our loop only accepts numeric non-negative ages:

$ java GetAge
Enter your age: 
f
You didn't input a valid age.
-10
Ages can't be negative.
5
Your age is: 5

Exercises

The biggest challenge in this section was understanding the flow of more complicated programs. Sometimes, it's helpful to draw a flow chart to understand what your code is doing. You can try some sample inputs to see what it outputs.

These exercises will help you get more comfortable with taking input and looping. This is a concept that will be used in a lot of your programs, so it's worth the extra practice.


  1. Write a program that asks the user to enter "yes" or "no" and continues looping until they enter either of these two inputs. Print out what the user enters outside the loop. Modify your program to also accept "y" and "n".

  2. Write a program that asks for a valid rock paper scissors move (either rock, paper, or scissors) and continues looping until the user enters one.

  3. In our GetAge program, we declared and initialized the age variable outside the loop. Could we have put it inside the loop or not initialized it? Why or why not?

  4. Why doesn't the GetAge program accept decimal ages like 5.0? Can you modify it to accept decimal ages?