Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Yes—you can build a small graphical game directly on a Raspberry Pi with Java. This guide creates a keyboard-controlled “Dodge the Falling Blocks” game using Swing/AWT, which comes with the JDK and avoids the additional ARM-specific setup that JavaFX can require. You will compile it with javac and run it on the Pi’s desktop.
The game targets about 60 simulation updates per second; that is a loop target, not a promise of 60 rendered frames per second on every model. It uses an 800 × 600 window, a player rectangle, and one falling block. No image assets, game engine, or GPIO wiring is needed.
What you need
Use a Raspberry Pi that can run the graphical Raspberry Pi OS desktop. A Pi 4 or Pi 5 is a comfortable development choice; a Pi Zero-class board may run a very simple game, but desktop responsiveness and compilation will be less comfortable. No particular frame rate or minimum memory is guaranteed here.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →- A Raspberry Pi and supported boot storage, such as a microSD card.
- A power supply appropriate for the selected model.
- A display and keyboard; a mouse is optional.
- Raspberry Pi OS Desktop and a JDK. A network connection is useful for installing packages.
- Basic familiarity with Java classes, methods, and loops.
Choose the Raspberry Pi OS edition recommended for your board in Raspberry Pi Imager. For Pi 4 or Pi 5, Raspberry Pi OS Desktop 64-bit is a practical starting point. Raspberry Pi OS is Debian-based, offers 32-bit and 64-bit editions, and uses APT; its documentation identified Debian Trixie as the current base when checked on August 18, 2026. For older hardware or compatibility issues, use Imager’s recommendation rather than forcing a 64-bit edition. See Raspberry Pi OS documentation and the Raspberry Pi Imager installation guide.
#1 Best Overall
- Includes Raspberry Pi 5 with 2.4Ghz 64-bit quad-core CPU (8GB RAM)
- Includes 128GB Micro SD Card pre-loaded with 64-bit Raspberry Pi OS, USB MicroSD Card Reader
- CanaKit Turbine Black Case for the Raspberry Pi 5
- CanaKit Low Noise Bearing System Fan
- Mega Heat Sink - Black Anodized
Raspberry Pi OS Lite has no desktop by default, so this windowed tutorial will not open there unless you separately configure a graphical environment or another display system. Raspberry Pi advises a clean installation for a major OS release change rather than treating it as a routine in-place package update.
Install and verify Java
In a terminal on the Pi, update the existing OS release and install a JDK, which includes both the Java runtime and compiler:
sudo apt update
sudo apt full-upgrade
sudo apt install openjdk-25-jdk
Raspberry Pi recommends full-upgrade for updating the current release because package dependencies can change. The OpenJDK 25 package may not be available on every image, architecture, or repository state. If APT cannot find it, inspect available packages and install the distribution’s default JDK instead:
Free tools Windows power users keep installed
One-click scans. No signup required.
apt search openjdk
sudo apt install default-jdk
Debian Trixie’s package metadata includes OpenJDK 25 source packages, but that does not mean every Pi image exposes the same package. Check the Debian Trixie OpenJDK package information and the OpenJDK installation guide if needed. Verify what is actually installed:
java --version
javac --version
Record the versions printed on your system; do not assume all Raspberry Pi OS images provide the same JDK release.
Why use Swing/AWT for the first game?
Swing/AWT provides a low-dependency path for a simple 2D game: JFrame creates a desktop window, Canvas gives it a drawing surface, and BufferStrategy helps present frames without the flicker associated with direct drawing. It is an older UI toolkit, but it remains useful for learning game loops, input, rendering, and collisions without bringing in an additional graphics runtime.
Rank #2
- Includes Raspberry Pi 5 with 2.4Ghz 64-bit quad-core CPU (4GB RAM)
- Includes 128GB Micro SD Card pre-loaded with 64-bit Raspberry Pi OS, USB MicroSD Card Reader
- CanaKit Turbine Black Case for the Raspberry Pi 5
- CanaKit Low Noise Bearing System Fan
- CanaKit Mega Heat Sink - Black Anodized
Other choices suit different needs:
| Approach | Strengths | Trade-off | Best fit |
|---|---|---|---|
| Swing/AWT | Included with a standard JDK; minimal setup for a small 2D game. | Older UI toolkit with fewer built-in game features. | First Raspberry Pi game and learning core mechanics. |
| JavaFX | Scene graph, controls, animation, and media. | JDK, JavaFX modules, runtime, and ARM architecture need to match. | A JavaFX-focused project prepared to verify its Pi runtime. |
| FXGL | Higher-level game abstractions and 2D game features. | Adds JavaFX and dependency/runtime compatibility considerations. | A larger game or JavaFX-based rapid prototyping. |
| Pi4J | Java access to GPIO and buses such as I2C, SPI, PWM, and serial. | Hardware I/O library, not a graphics or game engine. | Adding physical controls or electronics after the screen game works. |
| libGDX | Mature cross-platform framework for 2D and 3D games. | More framework and setup concepts than this small tutorial needs. | A broader cross-platform game project. |
For JavaFX, the OpenJFX setup documentation describes Maven dependencies and plugin configuration but is not Raspberry Pi-specific. Debian’s Trixie ARM64 OpenJFX listing exposes OpenJFX 11, while the JavaFX 25 builds page and FXGL project show why version matching matters. Pi4J’s JavaFX on Raspberry Pi example uses ARM-specific runtime and module configuration; treat its commands as specific to that setup, not universal current instructions. FXGL is a credible option for a larger game, but its repository and release notes describe a JavaFX/JDK-dependent baseline. For this build, Swing keeps the tutorial centered on game logic.
Windows 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 reinstallCrashes, 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 minuteCreate the game
The game repeats three jobs: read the current input state, update the simulation (movement, collisions, score, and game state), then render the current state. Separating those jobs avoids tying movement directly to how quickly the display happens to draw.
Keyboard
↓
Keyboard state
↓
Game update loop
├── player movement
├── falling block movement
├── collision detection
└── score and game state
↓
Canvas + BufferStrategy
↓
Raspberry Pi display
Make a directory and source file:
mkdir -p ~/java-games/dodge-game
cd ~/java-games/dodge-game
nano DodgeGame.java
Paste this complete program into DodgeGame.java. The player moves with the arrow keys or A/D. Each time the block falls past the screen, the score increases and its speed rises gradually. A collision ends the game; press R to restart.
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;
import java.util.Random;
import javax.swing.JFrame;
public final class DodgeGame extends Canvas implements Runnable, KeyListener {
private static final int WIDTH = 800;
private static final int HEIGHT = 600;
private static final int PLAYER_WIDTH = 50;
private static final int PLAYER_HEIGHT = 25;
private static final int BLOCK_SIZE = 30;
private final Random random = new Random();
private JFrame frame;
private Thread gameThread;
private volatile boolean running;
private boolean leftPressed;
private boolean rightPressed;
private boolean gameOver;
private int playerX;
private final int playerY = HEIGHT - 60;
private int blockX;
private int blockY;
private int blockSpeed;
private int score;
public DodgeGame() {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setFocusable(true);
addKeyListener(this);
resetGame();
}
private void createWindow() {
frame = new JFrame("Dodge Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(this);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
requestFocusInWindow();
}
private synchronized void start() {
if (running) {
return;
}
running = true;
gameThread = new Thread(this, "game-loop");
gameThread.start();
}
@Override
public void run() {
final double nsPerUpdate = 1_000_000_000.0 / 60.0;
double delta = 0;
long previous = System.nanoTime();
while (running) {
long current = System.nanoTime();
delta += (current - previous) / nsPerUpdate;
previous = current;
while (delta >= 1) {
update();
delta--;
}
render();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
running = false;
}
}
}
private void update() {
if (gameOver) {
return;
}
int playerSpeed = 6;
if (leftPressed) {
playerX -= playerSpeed;
}
if (rightPressed) {
playerX += playerSpeed;
}
playerX = Math.max(0, Math.min(WIDTH - PLAYER_WIDTH, playerX));
blockY += blockSpeed;
if (blockY > HEIGHT) {
blockY = -BLOCK_SIZE;
blockX = random.nextInt(WIDTH - BLOCK_SIZE + 1);
score++;
blockSpeed = Math.min(blockSpeed + 1, 15);
}
Rectangle player = new Rectangle(playerX, playerY, PLAYER_WIDTH, PLAYER_HEIGHT);
Rectangle block = new Rectangle(blockX, blockY, BLOCK_SIZE, BLOCK_SIZE);
if (player.intersects(block)) {
gameOver = true;
}
}
private void render() {
BufferStrategy buffer = getBufferStrategy();
if (buffer == null) {
if (isDisplayable()) {
createBufferStrategy(3);
}
return;
}
Graphics2D g = (Graphics2D) buffer.getDrawGraphics();
try {
g.setColor(Color.BLACK);
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.BLUE);
g.fillRect(playerX, playerY, PLAYER_WIDTH, PLAYER_HEIGHT);
g.setColor(Color.RED);
g.fillRect(blockX, blockY, BLOCK_SIZE, BLOCK_SIZE);
g.setColor(Color.WHITE);
g.drawString("Score: " + score, 20, 30);
if (gameOver) {
g.drawString("Game Over - press R to restart", 280, 300);
}
} finally {
g.dispose();
}
buffer.show();
}
private void resetGame() {
playerX = (WIDTH - PLAYER_WIDTH) / 2;
blockX = random.nextInt(WIDTH - BLOCK_SIZE + 1);
blockY = -BLOCK_SIZE;
blockSpeed = 4;
score = 0;
gameOver = false;
}
@Override
public void keyPressed(KeyEvent event) {
switch (event.getKeyCode()) {
case KeyEvent.VK_LEFT:
case KeyEvent.VK_A:
leftPressed = true;
break;
case KeyEvent.VK_RIGHT:
case KeyEvent.VK_D:
rightPressed = true;
break;
case KeyEvent.VK_R:
if (gameOver) {
resetGame();
}
break;
default:
break;
}
}
@Override
public void keyReleased(KeyEvent event) {
switch (event.getKeyCode()) {
case KeyEvent.VK_LEFT:
case KeyEvent.VK_A:
leftPressed = false;
break;
case KeyEvent.VK_RIGHT:
case KeyEvent.VK_D:
rightPressed = false;
break;
default:
break;
}
}
@Override
public void keyTyped(KeyEvent event) {
// This game uses key codes from keyPressed/keyReleased instead.
}
public static void main(String[] args) {
DodgeGame game = new DodgeGame();
game.createWindow();
game.start();
}
}
How the window and loop work
Canvas is the drawable area inside the JFrame. The window is made visible before the game thread starts. The loop accumulates elapsed nanoseconds and runs updates at a target of 60 per second, then renders the latest state. Rendering may happen at a different pace, depending on the Pi, desktop compositor, display, and workload. The short sleep helps avoid an unnecessarily busy loop; it is coarse pacing, not the timing mechanism itself.
BufferStrategy is created only once the canvas is displayable. A null strategy on the first render is therefore expected: the code creates it and returns, then draws on a later pass. Drawing uses a black background, blue player, red block, and white score. The graphics context is disposed in finally, and show() presents the completed frame.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
How input, movement, and collision work
The key listener changes booleans when keys are pressed and released. The update method samples those booleans, so holding a key moves continuously while game logic stays out of the event callback. The player position is clamped to the window, preventing it from moving beyond either edge.
Rank #3
- CanaKit Raspberry Pi 5 Essentials Starter Kit
The falling block moves down by blockSpeed each update. When it passes the bottom, it returns above the screen at a random horizontal position, adds one point, and speeds up to a cap. Rectangle.intersects performs a simple axis-aligned collision check. This is sufficient for one square obstacle; a larger game could represent enemies with a class or keep them in a list. Avoid allocating many temporary objects each frame in a more performance-sensitive project.
Compile and play
From the project directory, compile and launch the source:
javac DodgeGame.java
java DodgeGame
A window should appear with the player near the bottom and the block falling from above. Use the left/right arrow keys or A/D to dodge; the score increases each time the obstacle passes. After a collision, press R to restart.
The public class is named DodgeGame, so the source filename must be exactly DodgeGame.java. The game uses java.awt and javax.swing classes supplied by the JDK and needs no Maven or Gradle configuration.
Fix common problems
The window does not open
AWT needs a graphical display session. Raspberry Pi OS Lite, an inactive desktop session, or an SSH terminal without display forwarding can leave Java unable to connect to a display. Run the game from a terminal inside the Pi’s desktop session. These commands help inspect the session:
echo "$DISPLAY"
echo "$XDG_SESSION_TYPE"
A blank display variable in the process environment is a clue that the process cannot access the desktop display; SSH by itself does not put a window on the Pi’s physical screen.
Rank #4
- Includes Raspberry Pi 5 16GB with 2.4Ghz 64-bit quad-core CPU (16GB RAM)
- Includes 128GB Micro SD Card pre-loaded with 64-bit Raspberry Pi OS, USB MicroSD Card Reader
- CanaKit Turbine Black Case for the Raspberry Pi 5
- CanaKit Low Noise Bearing System Fan
- Mega Heat Sink - Black Anodized
The keyboard does not move the player
- Click the game window so it has focus.
- Confirm the canvas is focusable and the window calls
requestFocusInWindow()after becoming visible. - Try both the arrow keys and A/D. This game reads key codes in
keyPressedandkeyReleased, notkeyTyped. - Check that another component has not taken keyboard focus.
The buffer is null, or the screen flickers
A null buffer before the canvas becomes displayable is a normal first-run condition; this program creates the strategy once the canvas is ready. If drawing flickers, keep using the buffer strategy, dispose of the drawing context, and call show() as the program does. Do not repeatedly recreate the window.
The game speed varies or CPU use is high
Moving an object once per rendered frame makes behavior depend on how quickly the machine draws. This example uses elapsed time for fixed-step updates and renders separately. The target update rate does not guarantee identical display performance on every Pi. A loop with no pacing can consume a CPU core continuously; the brief sleep reduces that tendency but does not replace elapsed-time control.
JavaFX or Pi4J setup fails
JavaFX failures commonly come from mismatched JDK and JavaFX versions, the wrong ARM architecture, missing module-path settings, or instructions intended for a different runtime. Pi4J issues can involve major-version changes, plugins, numbering, permissions, or wiring. Pi4J says V2 and later are a rewrite rather than drop-in compatible with earlier versions; consult its compatibility notes and documentation for the chosen version.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Extend the game after it works
Organize a larger project
One file is reasonable for the first version because it makes the loop and state easy to see. As features grow, split responsibilities into classes, for example:
src/
Game.java
Player.java
Enemy.java
Input.java
Renderer.java
Multiple enemies, pause and restart states, levels, sound, images, and high scores are natural next steps. Keep input sampling, simulation updates, and drawing separate so each feature has a clear home.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsAdd physical buttons safely
Only add GPIO after the keyboard game works. Pi4J is the hardware I/O layer, not the renderer: a button event should update the same input state the game loop reads.
Best Value
- Includes Raspberry Pi 5 with 2.4Ghz 64-bit quad-core CPU (8GB RAM)
- Includes 32GB EVO+ Micro SD Card pre-loaded with 64-bit Pi OS, USB MicroSD Card Reader
- CanaKit Turbine Black Case for the Raspberry Pi 5
- CanaKit 45W PD Power Supply for the Raspberry Pi 5
- Display Cable - 6 foot (Supports up to 4K 60p)
GPIO button → Pi4J listener → game input state → update loop
Before wiring, check the Pi4J version and plugin against your board and OS. Use the Pi’s 3.3 V GPIO logic: do not connect a 5 V signal directly to a GPIO input. Configure a pull-up or pull-down, provide a suitable common ground, and account for switch debouncing. Do not block the rendering thread on a GPIO read. Pi4J’s current site lists V4.0.2, released June 8, 2026, built on Java 25; its API and hardware support should be checked for the selected version rather than assumed across major releases. See Pi4J and its technical documentation.
Use a Java game library or modern UI toolkit
If the project outgrows simple rectangles, FXGL provides higher-level Java/JavaFX game features, while JavaFX offers a scene graph and richer UI controls. Both introduce runtime and dependency choices that need verification for the Pi’s architecture and OS. FXGL’s project page describes its scope and setup; the release page gives version-specific release details. For serious cross-platform game development, libGDX is another framework option, though it is outside this minimal build.
Package the game as a JAR
Direct compilation is simplest while developing. To package compiled classes into an executable JAR in the same directory, run:
javac DodgeGame.java
jar cfe DodgeGame.jar DodgeGame *.class
java -jar DodgeGame.jar
The jar cfe command records DodgeGame as the entry point—the class containing public static void main(String[] args). A JAR is not a self-contained application by default: the Raspberry Pi still needs a compatible Java runtime. Bundling a runtime or creating a native package is a separate deployment task.
Once the software-only game is stable, you can launch it from a terminal or desktop shortcut. Autostart behavior depends on the Raspberry Pi OS desktop release and session, so use instructions specific to the edition you have rather than assuming a system-wide recipe applies.
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.

