Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
VGSources
Desktop Apps

Building a Minesweeper Game in Java: A Step-by-Step Guide

A practical Java Swing tutorial for building Minesweeper from scratch, including board logic, unique mines, flood fill, right-click flags, GUI wiring, tests, and packaging.

By VGSources Team 7 min read
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build a playable 9×9 Minesweeper game with 10 mines using Java Swing. The implementation separates a testable game model from the desktop interface, supports left-click revealing, right-click flags, flood-fill clearing, win/loss detection, reset, and command-line or IntelliJ execution.

This guide targets Java 17 or later and uses ordinary APIs, so it does not depend on Java 26-specific syntax. Swing is an older toolkit, but it remains a practical choice for a small educational desktop game. Oracle’s Swing tutorials explain the relevant components, layouts, listeners, and event-dispatch-thread rules, while noting that the tutorials were written for JDK 8: Oracle Swing trail.

What you will build

  • A configurable rectangular board, with a 9×9 beginner default and 10 mines.
  • Random, duplicate-free mine placement.
  • Adjacent-mine counts for every safe cell.
  • Left-click reveal and right-click flagging.
  • Automatic clearing of connected zero-value cells.
  • Win when every non-mine cell is revealed; loss when a mine is revealed.
  • A reset button and status display.

These are this tutorial’s rules, not universal Minesweeper rules. Flags are player markings, not proof of mines, and the initial version places mines before the first click, so an unlucky opening can lose immediately.

Install Java and create the project

Choose a JDK

Install a JDK, not only a JRE. Java 17 is a conservative language-level target. Java 26 was released on March 17, 2026, according to JetBrains, but this project does not require its newer features: Java 26 in IntelliJ IDEA. Free distributions are available from Oracle, Eclipse Temurin, Amazon Corretto, and Microsoft Build of OpenJDK.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use an IDE or the command line

IntelliJ IDEA’s current wizard lets you choose a JDK and the IntelliJ, Maven, or Gradle build system: new-project wizard. JetBrains describes core Java and Kotlin features as free in its unified product; Ultimate features are optional: unified IntelliJ IDEA.

A minimal source layout is:

src/main/java/com/example/minesweeper/
├── Main.java
├── Cell.java
├── GameState.java
├── GameBoard.java
└── MinesweeperFrame.java

For a first experiment, these classes may also be placed in one file named Minesweeper.java.

Model the game independently of Swing

Keeping rules out of button listeners makes the game testable and leaves room for a JavaFX front end later.

Game state and cell

public enum GameState {
    READY, PLAYING, WON, LOST
}

public final class Cell {
    private boolean mine;
    private boolean revealed;
    private boolean flagged;
    private int adjacentMines;

    public boolean isMine() { return mine; }
    public void setMine(boolean mine) { this.mine = mine; }
    public boolean isRevealed() { return revealed; }
    public void setRevealed(boolean revealed) { this.revealed = revealed; }
    public boolean isFlagged() { return flagged; }
    public void setFlagged(boolean flagged) { this.flagged = flagged; }
    public int getAdjacentMines() { return adjacentMines; }
    public void setAdjacentMines(int value) { this.adjacentMines = value; }
}

Create the board and place unique mines

Use row first and column second everywhere: cells[row][column]. Validate dimensions before allocating the board. Shuffling every coordinate avoids duplicate mines and remains simple to reason about.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
public final class GameBoard {
    private static final int[][] DIRECTIONS = {
        {-1, -1}, {-1, 0}, {-1, 1},
        { 0, -1},          { 0, 1},
        { 1, -1}, { 1, 0}, { 1, 1}
    };

    private final int rows;
    private final int columns;
    private final int mineCount;
    private final Random random = new Random();
    private Cell[][] cells;
    private GameState state;
    private int revealedSafeCells;

    public GameBoard(int rows, int columns, int mineCount) {
        if (rows <= 0 || columns <= 0 || mineCount < 0
                || mineCount >= rows * columns) {
            throw new IllegalArgumentException("Invalid board dimensions or mine count");
        }
        this.rows = rows;
        this.columns = columns;
        this.mineCount = mineCount;
        reset();
    }

    public void reset() {
        cells = new Cell[rows][columns];
        for (int row = 0; row < rows; row++)
            for (int column = 0; column < columns; column++)
                cells[row][column] = new Cell();

        List<Integer> positions = new ArrayList<>();
        for (int index = 0; index < rows * columns; index++) positions.add(index);
        Collections.shuffle(positions, random);
        for (int i = 0; i < mineCount; i++) {
            int index = positions.get(i);
            cells[index / columns][index % columns].setMine(true);
        }
        calculateCounts();
        revealedSafeCells = 0;
        state = GameState.PLAYING;
    }

    private boolean isInside(int row, int column) {
        return row >= 0 && row < rows && column >= 0 && column < columns;
    }

    private void calculateCounts() {
        for (int row = 0; row < rows; row++) {
            for (int column = 0; column < columns; column++) {
                if (cells[row][column].isMine()) continue;
                int count = 0;
                for (int[] direction : DIRECTIONS) {
                    int r = row + direction[0];
                    int c = column + direction[1];
                    if (isInside(r, c) && cells[r][c].isMine()) count++;
                }
                cells[row][column].setAdjacentMines(count);
            }
        }
    }

Counts must be calculated after every mine has been placed. The bounds helper handles corners and edges, where fewer than eight neighbors exist.

Implement revealing and flood fill

Guard model operations as well as UI controls. A flagged or already revealed cell is ignored. Mark a cell revealed before visiting neighbors; that prevents loops and repeated work.

    public void reveal(int row, int column) {
        if (state != GameState.PLAYING || !isInside(row, column)) return;
        Cell cell = cells[row][column];
        if (cell.isRevealed() || cell.isFlagged()) return;

        cell.setRevealed(true);
        if (cell.isMine()) {
            state = GameState.LOST;
            revealAllMines();
            return;
        }

        revealedSafeCells++;
        if (cell.getAdjacentMines() == 0) {
            for (int[] direction : DIRECTIONS)
                reveal(row + direction[0], column + direction[1]);
        }
        if (revealedSafeCells == rows * columns - mineCount)
            state = GameState.WON;
    }

    private void revealAllMines() {
        for (Cell[] row : cells)
            for (Cell cell : row)
                if (cell.isMine()) cell.setRevealed(true);
    }

This recursive version is readable for normal boards. For very large boards, replace it with a Deque<Position> queue: add the starting cell, remove one position at a time, reveal it, and enqueue eligible zero-value neighbors. An iterative flood fill avoids deep call stacks.

Add flagging and accessors

    public void toggleFlag(int row, int column) {
        if (state != GameState.PLAYING || !isInside(row, column)) return;
        Cell cell = cells[row][column];
        if (!cell.isRevealed()) cell.setFlagged(!cell.isFlagged());
    }

    public Cell getCell(int row, int column) { return cells[row][column]; }
    public int getRows() { return rows; }
    public int getColumns() { return columns; }
    public int getMineCount() { return mineCount; }
    public GameState getState() { return state; }

    public int flagCount() {
        int count = 0;
        for (Cell[] row : cells)
            for (Cell cell : row)
                if (cell.isFlagged()) count++;
        return count;
    }
}

A remaining-mine label can display mineCount - flagCount(). It may become negative if players place more flags than mines; that is a display choice, not a rules error.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build the Swing window

Swing components should be created on the event dispatch thread. Use a JPanel with GridLayout rather than absolute positioning. Oracle’s Swing material covers these components and concurrency concepts: Swing trail.

public final class Main {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            MinesweeperFrame frame = new MinesweeperFrame(9, 9, 10);
            frame.setVisible(true);
        });
    }
}

public final class MinesweeperFrame extends JFrame {
    private final GameBoard board;
    private final JButton[][] buttons;
    private final JLabel statusLabel = new JLabel();
    private final JLabel minesLabel = new JLabel();
    private final JPanel boardPanel;

    public MinesweeperFrame(int rows, int columns, int mines) {
        super("Minesweeper");
        board = new GameBoard(rows, columns, mines);
        buttons = new JButton[rows][columns];
        boardPanel = new JPanel(new GridLayout(rows, columns));

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout(8, 8));
        setResizable(false);

        JButton resetButton = new JButton("New game");
        resetButton.addActionListener(event -> {
            board.reset();
            refresh();
        });
        JPanel top = new JPanel(new FlowLayout(FlowLayout.LEFT));
        top.add(resetButton);
        top.add(minesLabel);
        top.add(statusLabel);
        add(top, BorderLayout.NORTH);
        add(boardPanel, BorderLayout.CENTER);
        buildButtons();
        refresh();
        pack();
        setLocationByPlatform(true);
    }

Connect left and right clicks

    private void buildButtons() {
        for (int row = 0; row < board.getRows(); row++) {
            for (int column = 0; column < board.getColumns(); column++) {
                JButton button = new JButton();
                button.setPreferredSize(new Dimension(42, 42));
                final int r = row;
                final int c = column;
                button.addMouseListener(new MouseAdapter() {
                    @Override public void mousePressed(MouseEvent event) {
                        if (SwingUtilities.isRightMouseButton(event))
                            board.toggleFlag(r, c);
                        else if (SwingUtilities.isLeftMouseButton(event))
                            board.reveal(r, c);
                        refresh();
                    }
                });
                buttons[row][column] = button;
                boardPanel.add(button);
            }
        }
    }

    private void refresh() {
        for (int row = 0; row < board.getRows(); row++)
            for (int column = 0; column < board.getColumns(); column++)
                updateButton(row, column);
        minesLabel.setText("Mines: " + (board.getMineCount() - board.flagCount()));
        statusLabel.setText("  " + board.getState());
    }

    private void updateButton(int row, int column) {
        Cell cell = board.getCell(row, column);
        JButton button = buttons[row][column];
        if (cell.isFlagged()) button.setText("F");
        else if (!cell.isRevealed()) button.setText("");
        else if (cell.isMine()) button.setText("*");
        else if (cell.getAdjacentMines() > 0)
            button.setText(Integer.toString(cell.getAdjacentMines()));
        else button.setText("");
        button.setEnabled(!cell.isRevealed() && board.getState() == GameState.PLAYING);
    }
}

Use SwingUtilities.isRightMouseButton and isLeftMouseButton rather than hard-coded button numbers. Trackpads may emulate a right click differently, so a later accessibility pass can add a Flag button or keyboard shortcut.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Improve feedback without mixing in rules

  • Color numbered cells by count in updateButton.
  • Use a persistent status label for playing, won, and lost states instead of relying only on a modal dialog.
  • Keep the model authoritative; never infer flags or mines from button text.
  • For large boards, use a preferred-size limit, a JScrollPane, or custom painting.
  • Do not disable revealed cells if you later add chord-click behavior.

Test the model before the GUI

Random boards are poor fixtures. Add a constructor or test helper that accepts deterministic mine coordinates, then test:

  • Dimensions, mine count, duplicate prevention, and invalid arguments.
  • Corner, edge, center, and no-mine adjacent counts.
  • Mine loss, zero-cell expansion, numbered boundaries, and repeated reveals.
  • Flag/unflag behavior, including flags on incorrect cells.
  • Victory after all safe cells are revealed, and no actions after win or loss.

A separated core with Swing and JavaFX implementations, Maven commands, and tests is also demonstrated by this community project: UNC GDSC Minesweeper.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Compile, run, and package

One-file command-line workflow

mkdir minesweeper
cd minesweeper
mkdir src out
javac -d out src/Minesweeper.java
java -cp out Minesweeper

With packages, compile and run the fully qualified class name:

javac -d out src/com/example/minesweeper/Main.java
java -cp out com.example.minesweeper.Main

Maven workflow

Maven is optional for Swing, but useful for JUnit tests and repeatable packaging. Use src/main/java, src/test/java, and a pom.xml, then run:

mvn test
mvn package

Maven is open source: official site. Gradle is another free build-tool option: official site.

Swing or JavaFX?

Choice Best for Trade-off
Swing Fast, dependency-free beginner desktop game Older widgets and more manual styling
JavaFX CSS, animation, and a polished scene graph Separate libraries and more involved packaging

JavaFX has not been bundled with the JDK since Java 11. IntelliJ documents dependency setup and jlink-based packaging for JavaFX applications: JavaFX setup and JavaFX packaging. Keep the model independent so a JavaFX rewrite changes presentation rather than game rules.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Useful extensions

  • Delay mine placement until the first reveal and exclude that cell, optionally its neighbors.
  • Replace recursive flood fill with a queue for arbitrary board sizes.
  • Add difficulty presets, a timer, high scores, save/load, and chord-click behavior.
  • Add keyboard controls and an alternate Flag action for accessibility.
  • Use custom painting when a button per cell becomes too large.

The Bottom Line

The reliable path is model first, Swing second: generate unique mines, calculate counts after placement, guard reveal and flag operations in the model, then render the model after every click. That structure gives you a working beginner game now and a codebase that can grow into tests, safer first clicks, larger boards, or a JavaFX interface.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from Patch Notes

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.