The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →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.
Recommended Free Tools
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.
Rank #2
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.
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.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →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.
Rank #4
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.
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.
Best Value
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.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallUseful 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.
Quick Recap
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.




