Skip to content

Java Project: Number Guessing Game

Updated 12 min read Aimerscrew

The number guessing game in Java is a classic beginner project, and for good reason. The computer picks a secret number, you guess, and it tells you "too high" or "too low" until you find it. Building it teaches you random numbers, loops with a limit, input validation, methods that return results and a small class that holds the game's state. Let's build it one step at a time.

🌍 Real-life example

Ever played "guess the price" at a party or on a TV game show? The host knows the real price of a blender, and each contestant hears only "higher" or "lower" after their guess. A smart player doesn't guess randomly: they split the range in half each time. Your program is the host, and the hints make that clever strategy possible.

What you'll build

The finished game:

  • picks a random secret number from 1 to 100,
  • gives the player 7 attempts and says Too high! or Too low! after each miss,
  • ignores text and out-of-range guesses without using up an attempt,
  • asks "Play again?" after each round,
  • remembers the best score (the fewest attempts) across rounds.

Concepts used in this project

ConceptWhere it's usedLesson
java.util.Random and seedsPicking the secret numberNew here (compare Math.random() in the Math class lesson)
for loop with a limitThe 7 attemptsfor loop
while (true) with returnAsking again after bad inputwhile loop, break and continue
Integer.parseInt() in try/catchRejecting text like fortytry-catch
A class with a constructor and fieldsGuessingGameclasses and objects, constructors
static final constantsLOW, HIGH, MAX_ATTEMPTSconstants
String methodstrim(), toLowerCase(), equals() for y/nstring methods

How do the hints help the player?

Every hint cuts away part of the range. After "Too high" for 50, the secret must be 1 to 49; after "Too low" for 25, it's 26 to 49, and so on. Halving the range each time means 7 guesses are always enough for 100 numbers, which is why the game allows exactly 7.

Number line from 1 to 100 shrinking with each hint: guess 50 is too high so the range becomes 1 to 49, guess 25 is too low so it becomes 26 to 49, guess 37 is too high so it becomes 26 to 36, and guess 31 is correct after 4 attempts
Each hint throws away part of the range. Here the secret 31 is found in 4 attempts.

Step 1: Pick a secret number with Random

The java.util.Random class makes random numbers. random.nextInt(100) returns a whole number from 0 to 99, so adding 1 gives 1 to 100. To make this lesson's output repeatable, the example creates the generator with a seed: new Random(42).

import java.util.Random;

public class RandomSecret {
    public static void main(String[] args) {
        Random random = new Random(42);

        for (int i = 1; i <= 5; i++) {
            int secret = random.nextInt(100) + 1;
            System.out.println("Secret number " + i + ": " + secret);
        }
    }
}
Secret number 1: 31
Secret number 2: 64
Secret number 3: 49
Secret number 4: 85
Secret number 5: 71

What's a seed? Random doesn't produce truly random numbers; it calculates a long sequence that looks random, starting from a seed value. The same seed always gives the same sequence, so you'll see exactly these five numbers when you run this code. new Random() without a seed picks a different starting point every time, which is what the real game needs.

💡 Tip: seeds make testing easy

Games and simulations often accept a seed so that a bug can be reproduced. Minecraft world seeds work the same way: share the seed and a friend gets the exact same world. In this project, the game class receives its Random through the constructor, so you can pass a seeded one while testing.

Step 2: Compare a guess with the secret

The hint logic is a simple comparison. Here it's tested with a fixed secret and a list of guesses, so you can check every branch without playing:

public class HintCheck {
    public static void main(String[] args) {
        int secret = 63;
        int[] guesses = {50, 75, 62, 63};

        for (int guess : guesses) {
            if (guess == secret) {
                System.out.println(guess + " -> Correct!");
            } else {
                System.out.println(guess + " -> " + hint(guess, secret));
            }
        }
    }

    static String hint(int guess, int secret) {
        if (guess < secret) {
            return "Too low!";
        }
        return "Too high!";
    }
}
50 -> Too low!
75 -> Too high!
62 -> Too low!
63 -> Correct!

Testing a small method on its own like this is a good habit. Once you trust hint(), you don't have to worry about it while building the rest of the game.

Step 3: Play one round with an attempts limit

Now build the GuessingGame class. It stores the Random and the Scanner it needs as fields, both passed in through the constructor. Its playRound() method picks the secret and loops at most MAX_ATTEMPTS times. It returns a number that tells the caller what happened: the attempts used to win, 0 for a loss, or INPUT_ENDED if the input ran out.

The main() method in this step passes new Random(42), so the secret is 31 every time and you can follow along exactly:

import java.util.Random;
import java.util.Scanner;

public class GuessingRound {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        GuessingGame game = new GuessingGame(new Random(42), input);
        game.playRound();
    }
}

class GuessingGame {
    static final int LOW = 1;
    static final int HIGH = 100;
    static final int MAX_ATTEMPTS = 7;
    static final int INPUT_ENDED = -1;

    private final Random random;
    private final Scanner input;

    GuessingGame(Random random, Scanner input) {
        this.random = random;
        this.input = input;
    }

    /** Plays one round. Returns the attempts used to win, 0 for a loss, or INPUT_ENDED. */
    int playRound() {
        int secret = random.nextInt(HIGH - LOW + 1) + LOW;
        System.out.println("I'm thinking of a number from " + LOW + " to " + HIGH + ".");
        System.out.println("You have " + MAX_ATTEMPTS + " attempts.");

        for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
            int guess = readGuess("Guess #" + attempt + ": ");
            if (guess == INPUT_ENDED) {
                return INPUT_ENDED;
            }
            if (guess == secret) {
                System.out.println("Correct! You got it in " + attempt + " attempts.");
                return attempt;
            }
            int attemptsLeft = MAX_ATTEMPTS - attempt;
            if (attemptsLeft > 0) {
                System.out.println(hint(guess, secret) + " Attempts left: " + attemptsLeft);
            } else {
                System.out.println(hint(guess, secret));
            }
        }
        System.out.println("Out of attempts! The number was " + secret + ".");
        return 0;
    }

    /** Asks until the guess is a whole number from LOW to HIGH. */
    private int readGuess(String prompt) {
        while (true) {
            System.out.print(prompt);
            if (!input.hasNextLine()) {
                System.out.println();
                return INPUT_ENDED;
            }
            String text = input.nextLine().trim();
            try {
                int guess = Integer.parseInt(text);
                if (guess >= LOW && guess <= HIGH) {
                    return guess;
                }
                System.out.println("Your guess must be from " + LOW + " to " + HIGH + ".");
            } catch (NumberFormatException e) {
                System.out.println("Please type a whole number.");
            }
        }
    }

    static String hint(int guess, int secret) {
        if (guess < secret) {
            return "Too low!";
        }
        return "Too high!";
    }
}

Here's a sample run (the user's typing follows each prompt):

I'm thinking of a number from 1 to 100.
You have 7 attempts.
Guess #1: 50
Too high! Attempts left: 6
Guess #2: forty
Please type a whole number.
Guess #2: 25
Too low! Attempts left: 5
Guess #3: 150
Your guess must be from 1 to 100.
Guess #3: 37
Too high! Attempts left: 4
Guess #4: 31
Correct! You got it in 4 attempts.

Notice what happened with the bad guesses. forty and 150 printed a message, and the prompt still said Guess #2 and Guess #3. That's because readGuess() has its own while (true) loop: it only returns once it has a valid number, so the for loop's attempt counter never moves for invalid input.

The secret is 31 because it's the first number from new Random(42), the same one you saw in Step 1.

📝 Note: why pass Random and Scanner in?

The class could create them itself, but receiving them through the constructor means the caller decides: a seeded Random for testing, an unseeded one for real games. This idea is called dependency injection, and you'll see it everywhere in professional Java code.

Step 4: Play again and track the best score

The last piece lives in main(). A loop plays rounds until the player says no. After each win, it compares the attempts with the best score so far. The variable bestScore starts at 0, meaning "no wins yet":

if (result > 0 && (bestScore == 0 || result < bestScore)) {
    bestScore = result;
    System.out.println("New best score: " + bestScore + " attempts!");
}

Read the condition aloud: "if the player won, and either there's no best score yet or this one is lower". The parentheses matter: without them, && would bind first and a loss could become the best score (see operator precedence).

askToPlayAgain() accepts y, yes, n or no in any capitals, thanks to trim().toLowerCase(), and keeps asking for anything else.

The complete number guessing game in Java

Save this as NumberGuessingGame.java and run it with java NumberGuessingGame.java. The only change to the GuessingGame class is that main() now passes an unseeded new Random():

import java.util.Random;
import java.util.Scanner;

public class NumberGuessingGame {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        GuessingGame game = new GuessingGame(new Random(), input);
        int bestScore = 0;

        System.out.println("=== Number Guessing Game ===");
        while (true) {
            System.out.println();
            int result = game.playRound();
            if (result == GuessingGame.INPUT_ENDED) {
                break;
            }
            if (result > 0 && (bestScore == 0 || result < bestScore)) {
                bestScore = result;
                System.out.println("New best score: " + bestScore + " attempts!");
            }
            if (bestScore > 0) {
                System.out.println("Best score so far: " + bestScore + " attempts.");
            }
            if (!askToPlayAgain(input)) {
                break;
            }
        }
        System.out.println("Thanks for playing!");
    }

    /** Returns true for y or yes, false for n, no or the end of input. */
    static boolean askToPlayAgain(Scanner input) {
        while (true) {
            System.out.print("Play again? (y/n): ");
            if (!input.hasNextLine()) {
                System.out.println();
                return false;
            }
            String answer = input.nextLine().trim().toLowerCase();
            if (answer.equals("y") || answer.equals("yes")) {
                return true;
            }
            if (answer.equals("n") || answer.equals("no")) {
                return false;
            }
            System.out.println("Please type y or n.");
        }
    }
}

class GuessingGame {
    static final int LOW = 1;
    static final int HIGH = 100;
    static final int MAX_ATTEMPTS = 7;
    static final int INPUT_ENDED = -1;

    private final Random random;
    private final Scanner input;

    GuessingGame(Random random, Scanner input) {
        this.random = random;
        this.input = input;
    }

    /** Plays one round. Returns the attempts used to win, 0 for a loss, or INPUT_ENDED. */
    int playRound() {
        int secret = random.nextInt(HIGH - LOW + 1) + LOW;
        System.out.println("I'm thinking of a number from " + LOW + " to " + HIGH + ".");
        System.out.println("You have " + MAX_ATTEMPTS + " attempts.");

        for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
            int guess = readGuess("Guess #" + attempt + ": ");
            if (guess == INPUT_ENDED) {
                return INPUT_ENDED;
            }
            if (guess == secret) {
                System.out.println("Correct! You got it in " + attempt + " attempts.");
                return attempt;
            }
            int attemptsLeft = MAX_ATTEMPTS - attempt;
            if (attemptsLeft > 0) {
                System.out.println(hint(guess, secret) + " Attempts left: " + attemptsLeft);
            } else {
                System.out.println(hint(guess, secret));
            }
        }
        System.out.println("Out of attempts! The number was " + secret + ".");
        return 0;
    }

    /** Asks until the guess is a whole number from LOW to HIGH. */
    private int readGuess(String prompt) {
        while (true) {
            System.out.print(prompt);
            if (!input.hasNextLine()) {
                System.out.println();
                return INPUT_ENDED;
            }
            String text = input.nextLine().trim();
            try {
                int guess = Integer.parseInt(text);
                if (guess >= LOW && guess <= HIGH) {
                    return guess;
                }
                System.out.println("Your guess must be from " + LOW + " to " + HIGH + ".");
            } catch (NumberFormatException e) {
                System.out.println("Please type a whole number.");
            }
        }
    }

    static String hint(int guess, int secret) {
        if (guess < secret) {
            return "Too low!";
        }
        return "Too high!";
    }
}

Sample run

Because the secret is random, your numbers will differ every time you play. Here's one real game (the user's typing follows each prompt). The player loses the first round, types maybe at the replay question, then wins the second round:

=== Number Guessing Game ===

I'm thinking of a number from 1 to 100.
You have 7 attempts.
Guess #1: 50
Too low! Attempts left: 6
Guess #2: 80
Too low! Attempts left: 5
Guess #3: 90
Too low! Attempts left: 4
Guess #4: 95
Too low! Attempts left: 3
Guess #5: abc
Please type a whole number.
Guess #5: 97
Too low! Attempts left: 2
Guess #6: 98
Too low! Attempts left: 1
Guess #7: 99
Too low!
Out of attempts! The number was 100.
Play again? (y/n): maybe
Please type y or n.
Play again? (y/n): y

I'm thinking of a number from 1 to 100.
You have 7 attempts.
Guess #1: 50
Too low! Attempts left: 6
Guess #2: 75
Too high! Attempts left: 5
Guess #3: 60
Too high! Attempts left: 4
Guess #4: 55
Too high! Attempts left: 3
Guess #5: 52
Too high! Attempts left: 2
Guess #6: 51
Correct! You got it in 6 attempts.
New best score: 6 attempts!
Best score so far: 6 attempts.
Play again? (y/n): n
Thanks for playing!

Common mistakes in this project

1. An off-by-one secret

int secret = random.nextInt(100);      // 0 to 99: wrong
int secret = random.nextInt(100) + 1;  // 1 to 100: right

nextInt(n) never returns n itself, and it can return 0. Always add the lowest value you want.

2. Creating a new Random every round with the same seed

If you write new Random(42) inside playRound(), every round gets the same secret. Create the Random once and reuse it; in the real game, don't seed it at all.

3. Counting invalid input as an attempt

If you parse the guess inside the for loop and simply continue on an error, the attempt counter still goes up. Keeping validation in a separate readGuess() method that only returns valid guesses fixes this neatly.

4. Looping forever when the input ends

Calling nextLine() without checking hasNextLine() throws a NoSuchElementException when there's no more input. Worse, a validation loop that catches every exception can spin forever. Always check hasNextLine() first and stop when it's false.

Level up: challenges

🏋️ Challenges

  1. Difficulty levels. Ask for Easy (1–50, 10 attempts), Normal (1–100, 7) or Hard (1–1000, 10) before each round. Turn LOW, HIGH and MAX_ATTEMPTS into constructor parameters.
  2. Warmer or colder. After a miss, also say "Very close!" when the guess is within 3 of the secret. Math.abs(guess - secret) gives the distance.
  3. No repeated guesses. Store guesses in a HashSet<Integer> and tell the player when they repeat one, without using an attempt.
  4. Proper grammar. Print "1 attempt" instead of "1 attempts" using the ternary operator.
  5. Swap roles. Make the computer guess your number. You answer h, l or c, and it always guesses the middle of the remaining range.
Show a hint for challenge 2
static String hint(int guess, int secret) {
    String direction = guess < secret ? "Too low!" : "Too high!";
    if (Math.abs(guess - secret) <= 3) {
        return direction + " Very close!";
    }
    return direction;
}
Show a hint for challenge 5

Keep two variables, low = 1 and high = 100. Each guess is (low + high) / 2. If the player says h (too high), set high = guess - 1; for l, set low = guess + 1. If low ever becomes greater than high, the player gave inconsistent answers.

What's next?

You've built a game with state, rules and replay. Next, you'll manage a whole collection of objects with your own exceptions: the library management system in Java.

Key takeaways

  • A number guessing game in Java picks a secret with random.nextInt(100) + 1 and loops over guesses, answering Too high or Too low.
  • The same Random seed always produces the same numbers, which makes testing repeatable; new Random() without a seed changes every run.
  • Read guesses with nextLine() and Integer.parseInt() inside try/catch, and validate the range before accepting a guess.
  • Keep validation in its own method so bad input never uses up one of the player's attempts.
  • Passing Random and Scanner into the constructor lets the caller choose a seeded generator for tests and a real one for play.
  • A play-again loop plus a bestScore variable that starts at 0 lets you track the fewest attempts across rounds.

Frequently asked questions

How do you generate a random number from 1 to 100 in Java?

Create a java.util.Random object and call random.nextInt(100) + 1. nextInt(100) returns 0 to 99, so adding 1 shifts the range to 1 to 100. Math.random() also works: (int) (Math.random() * 100) + 1.

Why does my Java guessing game pick the same number every time?

You probably created the Random with a fixed seed, such as new Random(42), or you create a new seeded Random every round. Use new Random() once, without a seed, and reuse it for every round.

How many guesses are needed to find a number from 1 to 100?

At most 7, if you always guess the middle of the remaining range. Each guess halves the range, and 2 to the power of 7 is 128, which is more than 100. This strategy is called binary search.

How do I stop the game from crashing when the user types a word?

Read the whole line with nextLine() and convert it with Integer.parseInt() inside a try block. Catch NumberFormatException, print a message and ask again instead of counting the attempt.

How do I add a play again option in Java?

Wrap the round in a loop. After each round, ask "Play again? (y/n)", read the answer with nextLine(), and break out of the loop unless the answer is y or yes.