← thecodex.expert · The Codex Family of Knowledge
Tier 0 · Absolute Beginner · Java Project

Number Guessing Game

Your very first Java program with a memory and a decision: the computer picks a secret number, and you guess until you get it. You will learn how a program asks a question, checks an answer, and repeats.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a tiny game: the computer thinks of a number between 1 and 100, and the player keeps guessing. After each guess the computer says “too high” or “too low” until the player gets it right. Simple — but it teaches the three things every program does: ask, decide, and repeat.

Where this shows up in real life: every time an app checks your password, validates a form, or keeps asking until you give a valid answer, it is doing exactly this — take input, compare it to something, and loop until the condition is met. Learn it here in 15 lines, and you have learned the heartbeat of almost every program.

2 How to Think About It

Before writing any code, picture the game as a loop with a decision inside it. You do not need to know Java yet — you just need to know the shape of what happens:

The plan — in plain English
1. Pick a secret number once, at the start. → 2. Ask the player to guess. → 3. Compare the guess to the secret: lower, higher, or equal? → 4. If not equal, go back to step 2. If equal, celebrate and stop. That is the whole program. Everything below is just saying this in Java.

Too low

Too high

Correct!

Computer picks a secret number 1-100

Ask the player to guess

Is the guess right?

Say 'too low'

Say 'too high'

Show how many guesses it took

Game over

3 The Build — explained part by part

Here is the complete game, split into a testable playGame method and a tiny main that wires it to real stdin/stdout. Read each part’s plain-English note below it.

JavaNumberGuessingGame.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Random;

/**
 * Number Guessing Game: the computer picks a secret number between 1 and 100,
 * and the player guesses until they get it, hearing "too high" or "too low"
 * after every wrong guess.
 */
public class NumberGuessingGame {

    /**
     * Plays one full game against a given secret number, reading guesses from
     * {@code in} and writing prompts/feedback to {@code out}. Kept separate
     * from {@code main} so it can be tested without a real terminal.
     *
     * @return the number of guesses it took to win
     */
    static int playGame(int secret, BufferedReader in, PrintStream out) throws IOException {
        int guesses = 0;
        while (true) {
            out.print("Guess a number between 1 and 100: ");
            String line = in.readLine();
            if (line == null) {
                throw new IllegalStateException("No more input.");
            }
            int guess;
            try {
                guess = Integer.parseInt(line.trim());
            } catch (NumberFormatException e) {
                out.println("That's not a whole number. Try again.");
                continue;
            }
            guesses++;
            if (guess < secret) {
                out.println("Too low.");
            } else if (guess > secret) {
                out.println("Too high.");
            } else {
                out.println("Correct! You got it in " + guesses + " guesses.");
                return guesses;
            }
        }
    }

    public static void main(String[] args) throws IOException {
        int secret = new Random().nextInt(100) + 1; // once, before the loop starts
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        playGame(secret, in, System.out);
    }
}
⚠ No in-browser playground here
Java compiles to JVM bytecode and needs a real JDK to run, so unlike the Python version of this project there is no editor above you can run in the browser. Copy the code below and run it on your own machine — it takes seconds once a JDK is installed.
What each part does — in plain words
static int playGame(int secret, BufferedReader in, PrintStream out) — the whole game logic takes its input and output as parameters instead of reaching for System.in/System.out directly. That is what lets the tests below feed it fake input through a StringReader and capture its output in a ByteArrayOutputStream, with no real terminal involved.

while (true) { ... } — Java has no dedicated infinite-loop keyword the way Rust does, so while (true) is the idiomatic way to say “keep going until something inside explicitly returns.”

Integer.parseInt(line.trim()) wrapped in a try/catch for NumberFormatException — unlike Rust’s Result-returning .parse(), Java’s numeric parsing throws an unchecked exception on bad input, so catching it explicitly is what stands between a typo and a crashed program.

new Random().nextInt(100) + 1 — nextInt(100) returns 0–99, so adding 1 shifts the range to the 1–100 the game promises. Called exactly once, before the loop starts — the classic mistake below shows what happens if you get that placement wrong.
Common mistakes — and how to avoid them
✗ Calling new Random().nextInt(100) inside the loop instead of once before it — the secret would change every guess and the game could never be won.
✓ Pick the secret once, before playGame starts looping.
✗ Writing if (guess = secret) by mistake instead of ==.
✓ Java will not even compile this one when the types don’t line up as a boolean — here both sides are int, so watch for it manually; it is a classic typo in every C-family language.
✗ Letting Integer.parseInt throw uncaught — a non-numeric guess would crash the whole program with a stack trace instead of a friendly message.
✓ Catch NumberFormatException explicitly, as playGame does, and continue the loop on a parse error.

4 Test & Prove Each Part

How do we know the game works without playing it by hand? We write small tests — each one checks one rule and proves it. Java has no built-in test framework, so these use JUnit, the de facto standard covered in the course’s Testing lesson.

Guessing the secret on the first try counts as 1 guess
The game correctly narrows down with a mix of too-low and too-high guesses
Non-numeric input is rejected with a friendly message, not a crash
Every guess counts, including the wrong ones
JavaNumberGuessingGameTest.java
import org.junit.Test;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.StringReader;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class NumberGuessingGameTest {

    private String run(int secret, String input) throws Exception {
        BufferedReader in = new BufferedReader(new StringReader(input));
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        PrintStream out = new PrintStream(bytes);
        NumberGuessingGame.playGame(secret, in, out);
        return bytes.toString();
    }

    @Test
    public void firstGuessCorrectTakesOneGuess() throws Exception {
        String output = run(42, "42\n");
        assertTrue(output.contains("Correct! You got it in 1 guesses."));
    }

    @Test
    public void narrowsDownWithHighAndLowGuesses() throws Exception {
        String output = run(37, "50\n25\n37\n");
        assertTrue(output.contains("Too high."));
        assertTrue(output.contains("Too low."));
        assertTrue(output.contains("Correct! You got it in 3 guesses."));
    }

    @Test
    public void rejectsNonNumericInputWithoutCrashing() throws Exception {
        String output = run(10, "banana\n10\n");
        assertTrue(output.contains("That's not a whole number. Try again."));
        assertTrue(output.contains("Correct! You got it in 1 guesses."));
    }

    @Test
    public void countsEveryGuessIncludingWrongOnes() throws Exception {
        BufferedReader in = new BufferedReader(new StringReader("1\n2\n3\n4\n5\n"));
        PrintStream out = new PrintStream(new ByteArrayOutputStream());
        int guesses = NumberGuessingGame.playGame(5, in, out);
        assertEquals(5, guesses);
    }
}

Compile and run with javac -cp junit-4.13.2.jar and hamcrest-core-1.3.jar NumberGuessingGame.java NumberGuessingGameTest.java then java -cp .:junit-4.13.2.jar:hamcrest-core-1.3.jar org.junit.runner.JUnitCore NumberGuessingGameTest. A real project would pull JUnit in through Maven or Gradle instead of a standalone jar — see the course’s Build Tools lesson — but the two jars above are exactly what those build tools download for you under the hood, so the tests themselves are identical either way. A StringReader stands in for real stdin, which is exactly what the testable playGame(secret, in, out) signature was designed to make possible.

5 The Interface

Even a tiny program has an interface — the way a person interacts with it. Here is its contract, documented plainly, the same way a professional would describe any tool.

INPUTYour guessa whole number 1–100, typed by the player
What it expects
A number like 42, typed and then Enter pressed.
OUTPUTFeedbackone of three replies
What it returns
"Too low."
"Too high."
"Correct! You got it in N guesses."

6 Run It & Automate It

Save the code as NumberGuessingGame.java and compile it with javac — that turns your source into .class bytecode files, which java then runs on the JVM. No separate install step: any real JDK ships both tools.

Run it locally
javac NumberGuessingGame.java && java NumberGuessingGame
Then type a number, press Enter, and follow the “too high / too low” hints until you win.

A CI tool like Jenkins runs the same compile-then-test steps automatically whenever the code changes — every line below has a plain explanation.

What you should see when it works
Terminala real run
Guess a number between 1 and 100: 50
Too high.
Guess a number between 1 and 100: 25
Too low.
Guess a number between 1 and 100: 37
Too high.
Guess a number between 1 and 100: 31
Correct! You got it in 4 guesses.
If it breaks — how to fix it
🚨 java.lang.NumberFormatException: For input string: "abc"
This exception is exactly what the try/catch around Integer.parseInt is meant to catch. If you see it crash the program, check that the catch block wraps the parse call, not just the comparison after it.
🚨 The game seems to ignore my guess or always says the same thing.
Check that you are comparing guess to secret and not to a copy that never updates — and that the loop actually returns on a correct guess.
🚨 error: cannot find symbol
Usually a typo in a method or variable name, or forgetting to compile NumberGuessingGame.java before NumberGuessingGameTest.java when compiling by hand one file at a time (compiling them together, as shown above, avoids this).
GroovyJenkinsfile
// Jenkinsfile — runs the tests automatically every time the code changes.
pipeline {
    agent any                          // run on any available machine
    environment {
        CP = 'junit-4.13.2.jar:hamcrest-core-1.3.jar'   // JUnit + its one dependency
    }

    stages {
        stage('Get the code') {
            steps { checkout scm }     // download the latest code
        }
        stage('Set up JDK') {
            steps {
                sh 'java -version'           // confirm a JDK is installed
                sh 'javac -cp "$CP" *.java'   // compile the program and its tests together
            }
        }
        stage('Run the tests') {
            steps {
                sh 'java -cp ".:$CP" org.junit.runner.JUnitCore NumberGuessingGameTest'
            }
        }
    }

    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 Try this next — make it yours
  1. Limit the attempts. Give the player only 7 guesses. (Teaches: counting down, and an early return on failure.)
  2. Add difficulty levels. Let the player choose a range like 1–1000. (Teaches: passing parameters into Random.nextInt.)
  3. Switch to SecureRandom. Swap java.util.Random for java.security.SecureRandom, the same type the password generator project uses. (Teaches: when predictability actually matters.)
What you learned
You learned Java’s idiomatic while (true) loop-with-a-return, catching NumberFormatException instead of letting bad input crash the program, and a genuinely useful pattern — writing core logic against BufferedReader/PrintStream parameters so it can be tested without touching a real terminal. Related: Control Flow, Exception Handling.