A type-along manual: install a JDK, write the game in five small steps with the whole file shown at each step, run it from source, compile it, build a jar, make it your own, and learn to read javac's error messages.
▶Prefer to watch? This short video runs this page's main commands in a real lab, with on-screen captions and no voiceover. Click to play, or watch it on YouTube.
java Hangman.java
javac Hangman.java && java Hangman
java -jar hangman.jar
The same Linux command hangman as the Python guide in this series, written in Java. You type a short program in a text editor, save it, run it, and add a little more each step. By the end you have a 94-line game that picks a random Linux command (ls, grep, chmod, systemctl, journalctl, rsync...), shows its meaning as a hint and lets you guess it letter by letter, and a hangman.jar that runs on any machine with Java.
Java is a good second language for a sysadmin to meet: a lot of server software runs on it, and packaging a Java program shows what changes when a program needs a runtime. This jar is what the next guide in the series, Package a Java App as a .deb and an .rpm, Side by Side, turns into packages for Ubuntu and for Rocky Linux.
Every example ran for real, on Ubuntu 24.04 with OpenJDK 21.0.12. The finished game was also compiled and run on Rocky Linux 9.8 with OpenJDK 17.0.21; it uses nothing that is not in the standard Java library. The random word in the examples is fixed with the HANGSEED variable (chmod unless another word is named), which the game supports so that it can be tested; on your machine the word is random, so guess the letters of your own word.
Install Java and check it
Java programs need a JDK (Java Development Kit) to be written and built, and only a JRE (the runtime) to be run. The JDK contains both. Check what you have:
$ java -version
openjdk version "21.0.12.1" 2026-08-18
OpenJDK Runtime Environment (build 21.0.12.1+1-1-24.04.4-Ubuntu)
OpenJDK 64-Bit Server VM (build 21.0.12.1+1-1-24.04.4-Ubuntu, mixed mode, sharing)
$ javac -version
javac 21.0.12.1
java runs programs; javac is the compiler. If either says "command not found", install a JDK. The "headless" packages leave out the graphical parts, which a command-line game does not need. On Ubuntu and Debian:
$ sudo apt install default-jdk-headless
On Rocky Linux, RHEL and Fedora (Rocky 9 offers 17 and 21; 17 is used in the next guide):
$ sudo dnf install java-17-openjdk-devel
Everything in this guide ran on Ubuntu 24.04 with OpenJDK 21. The finished game was also compiled and run on Rocky Linux 9.8 with OpenJDK 17. It uses only classes that come with Java.
Make a folder for the project and go into it:
$ mkdir ~/jhangman
$ cd ~/jhangman
$ pwd
/home/tc/jhangman
Use any text editor; nano is the easiest in a terminal (nano Hangman.java, Ctrl+O then Enter saves, Ctrl+X leaves). Java is stricter than Python about two things you will see at once: every statement ends with a semicolon, and blocks are wrapped in curly braces. The indentation is only for people, so keep it tidy anyway: four spaces.
Step 1: your first program
In Java every program lives in a class, and the file must be named after the class: class Hangman goes in Hangman.java, with exactly that capital H. Type this:
$ cat Hangman.java
// Hangman - guess the Linux command before the stick figure is finished
import java.util.*;
public class Hangman {
public static void main(String[] args) {
System.out.println("=== cmd-hangman by TechCommune (Java) ===");
}
}
What the lines mean:
// ... a comment: ignored, a note for people
import java.util.*; makes the standard collection classes (Map, List, Random, Scanner...) available; the star means "all of them"
public class Hangman { the class: everything below, up to the last }, belongs to it
public static void main(...) the starting point: Java begins here. You will always write it exactly like this
System.out.println(...) print a line
Java normally needs two steps, compile then run. Since Java 11 a single-file program can be run straight from its source, which is perfect for learning:
$ java Hangman.java
=== cmd-hangman by TechCommune (Java) ===
Java compiled it in memory and ran it. If something is wrong, the next-to-last section shows the messages you may see.
Step 2: a list of commands, and one at random
The game needs the Linux commands and their hints. In Java that is a Map: each key (the command) has a value (its hint). LinkedHashMap keeps the entries in the order you add them. A static { ... } block runs once, when the class is loaded, and fills the map. main now picks one entry at random:
$ cat Hangman.java
// Hangman - guess the Linux command before the stick figure is finished
import java.util.*;
public class Hangman {
static final Map<String, String> WORDS = new LinkedHashMap<>();
static {
WORDS.put("ls", "list directory contents");
WORDS.put("cd", "change directory");
WORDS.put("pwd", "print the current directory");
WORDS.put("grep", "search text for a pattern");
WORDS.put("chmod", "change file permissions");
WORDS.put("chown", "change file owner");
WORDS.put("mkdir", "make a directory");
WORDS.put("tail", "show the end of a file");
WORDS.put("systemctl", "control system services");
WORDS.put("journalctl", "read the system journal");
WORDS.put("rsync", "copy files efficiently");
WORDS.put("ssh", "log in to another machine");
WORDS.put("crontab", "schedule repeating jobs");
WORDS.put("kill", "stop a process");
WORDS.put("df", "show free disk space");
}
public static void main(String[] args) {
Random random = new Random();
String seed = System.getenv("HANGSEED");
if (seed != null) {
random = new Random(Long.parseLong(seed));
}
List<String> names = new ArrayList<>(WORDS.keySet());
System.out.println(names.get(random.nextInt(names.size())));
}
}
New in this step:
Map<String, String> a map from text to text
WORDS.put(key, value) add one entry
Random a random-number generator; nextInt(n) gives 0 to n-1
System.getenv("HANGSEED") reads an environment variable, or null when it is not set. If it is set, the generator starts from that number, so the "random" word is the same every time. This is for testing: it is how the examples in this guide always show the same word. Normally you do not set it, and every game is different
List<String> names = new ArrayList<>(WORDS.keySet()) the keys as a list, so we can pick one by position
Run it (HANGSEED is set to 8 for the examples; your word will differ):
$ java Hangman.java
chmod
$ java Hangman.java
kill
The same program printed a different word because the starting number was different (8 and 2 here; without HANGSEED the word differs on every run).
Step 3: the stick figure
Seven pictures in an array, from an empty gallows to the finished figure. An array is a numbered list: the first element is number 0. Add it below the map, and let main print the last picture to check that it is right:
$ cat Hangman.java
// Hangman - guess the Linux command before the stick figure is finished
import java.util.*;
public class Hangman {
static final Map<String, String> WORDS = new LinkedHashMap<>();
static {
WORDS.put("ls", "list directory contents");
WORDS.put("cd", "change directory");
WORDS.put("pwd", "print the current directory");
WORDS.put("grep", "search text for a pattern");
WORDS.put("chmod", "change file permissions");
WORDS.put("chown", "change file owner");
WORDS.put("mkdir", "make a directory");
WORDS.put("tail", "show the end of a file");
WORDS.put("systemctl", "control system services");
WORDS.put("journalctl", "read the system journal");
WORDS.put("rsync", "copy files efficiently");
WORDS.put("ssh", "log in to another machine");
WORDS.put("crontab", "schedule repeating jobs");
WORDS.put("kill", "stop a process");
WORDS.put("df", "show free disk space");
}
static final String[] STAGES = {
"\n\n\n\n\n_______",
"\n |\n |\n |\n |\n_|_____",
" ______\n |\n |\n |\n |\n_|_____",
" ______\n | |\n | O\n |\n |\n_|_____",
" ______\n | |\n | O\n | |\n |\n_|_____",
" ______\n | |\n | O\n | /|\\\n |\n_|_____",
" ______\n | |\n | O\n | /|\\\n | / \\\n_|_____"
};
public static void main(String[] args) {
Random random = new Random();
String seed = System.getenv("HANGSEED");
if (seed != null) {
random = new Random(Long.parseLong(seed));
}
List<String> names = new ArrayList<>(WORDS.keySet());
System.out.println(names.get(random.nextInt(names.size())));
System.out.println(STAGES[6]);
}
}
String[] STAGES = { ... } an array of text. Inside a Java string the two characters \n mean a new line, and a real backslash has to be written twice (\\): that is why the figure's arms are written "/|\\"
Run it:
$ java Hangman.java
chmod
______
| |
| O
| /|\
| / \
_|_____
Step 4: one round of the game
The game itself is a method called play. It picks a word, then repeats: show the picture and the blanks, ask for a letter, and either reveal it or count a miss. The loop ends in a win (all letters found) or a loss (the last picture reached). main now creates a Scanner, which reads what the player types, and calls play:
$ cat Hangman.java
// Hangman - guess the Linux command before the stick figure is finished
import java.util.*;
public class Hangman {
static final Map<String, String> WORDS = new LinkedHashMap<>();
static {
WORDS.put("ls", "list directory contents");
WORDS.put("cd", "change directory");
WORDS.put("pwd", "print the current directory");
WORDS.put("grep", "search text for a pattern");
WORDS.put("chmod", "change file permissions");
WORDS.put("chown", "change file owner");
WORDS.put("mkdir", "make a directory");
WORDS.put("tail", "show the end of a file");
WORDS.put("systemctl", "control system services");
WORDS.put("journalctl", "read the system journal");
WORDS.put("rsync", "copy files efficiently");
WORDS.put("ssh", "log in to another machine");
WORDS.put("crontab", "schedule repeating jobs");
WORDS.put("kill", "stop a process");
WORDS.put("df", "show free disk space");
}
static final String[] STAGES = {
"\n\n\n\n\n_______",
"\n |\n |\n |\n |\n_|_____",
" ______\n |\n |\n |\n |\n_|_____",
" ______\n | |\n | O\n |\n |\n_|_____",
" ______\n | |\n | O\n | |\n |\n_|_____",
" ______\n | |\n | O\n | /|\\\n |\n_|_____",
" ______\n | |\n | O\n | /|\\\n | / \\\n_|_____"
};
static void play(Scanner in, Random random) {
List<String> names = new ArrayList<>(WORDS.keySet());
String word = names.get(random.nextInt(names.size()));
String hint = WORDS.get(word);
Set<Character> guessed = new HashSet<>();
int misses = 0;
while (misses < STAGES.length - 1) {
System.out.println(STAGES[misses]);
StringBuilder shown = new StringBuilder();
boolean complete = true;
for (char c : word.toCharArray()) {
if (guessed.contains(c)) {
shown.append(c).append(' ');
} else {
shown.append("_ ");
complete = false;
}
}
System.out.println("\nCommand: " + shown.toString().trim());
System.out.println("Hint: " + hint);
if (complete) {
System.out.println("\nYou got it! The command was: " + word);
return;
}
System.out.print("Guess a letter: ");
String letter = in.nextLine().trim().toLowerCase();
if (letter.length() != 1 || !Character.isLetter(letter.charAt(0))) {
System.out.println("Type a single letter.");
} else if (guessed.contains(letter.charAt(0))) {
System.out.println("You already tried that one.");
} else if (word.contains(letter)) {
guessed.add(letter.charAt(0));
} else {
guessed.add(letter.charAt(0));
misses++;
System.out.println("Nope!");
}
}
System.out.println(STAGES[misses]);
System.out.println("\nGame over. The command was: " + word);
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Random random = new Random();
String seed = System.getenv("HANGSEED");
if (seed != null) {
random = new Random(Long.parseLong(seed));
}
System.out.println("=== cmd-hangman by TechCommune (Java) ===");
play(in, random);
}
}
The new ideas:
while (misses < STAGES.length - 1) { ... } repeat while the player has misses left; STAGES.length is the length of the array
Set<Character> guessed = new HashSet<>() the letters tried, with no duplicates
for (char c : word.toCharArray()) go through the word one letter at a time
in.nextLine().trim().toLowerCase() read a line, remove spaces around it, make it lower case
if / else if / else the checks: not a single letter, already tried, right, wrong
return leave the method (a win)
Play a round where the letters z and q are wrong, then win (the word is chmod):
$ java Hangman.java
=== cmd-hangman by TechCommune (Java) ===
_______
Command: _ _ _ _ _
Hint: change file permissions
Guess a letter: c
_______
Command: c _ _ _ _
Hint: change file permissions
Guess a letter: z
Nope!
|
|
|
|
_|_____
Command: c _ _ _ _
Hint: change file permissions
Guess a letter: h
|
|
|
|
_|_____
Command: c h _ _ _
Hint: change file permissions
Guess a letter: q
Nope!
______
|
|
|
|
_|_____
Command: c h _ _ _
Hint: change file permissions
Guess a letter: m
______
|
|
|
|
_|_____
Command: c h m _ _
Hint: change file permissions
Guess a letter: o
______
|
|
|
|
_|_____
Command: c h m o _
Hint: change file permissions
Guess a letter: d
______
|
|
|
|
_|_____
Command: c h m o d
Hint: change file permissions
You got it! The command was: chmod
And a loss: six letters that are not in chmod (only the end of the output is shown):
$ java Hangman.java
(earlier screens left out)
Hint: change file permissions
Guess a letter: g
Nope!
______
| |
| O
| /|\
|
_|_____
Command: _ _ _ _ _
Hint: change file permissions
Guess a letter: i
Nope!
______
| |
| O
| /|\
| / \
_|_____
Game over. The command was: chmod
Step 5: the finished game
One thing is left: play again. main now loops, and asks after each round. This is the complete file:
$ cat Hangman.java
// Hangman - guess the Linux command before the stick figure is finished
import java.util.*;
public class Hangman {
static final Map<String, String> WORDS = new LinkedHashMap<>();
static {
WORDS.put("ls", "list directory contents");
WORDS.put("cd", "change directory");
WORDS.put("pwd", "print the current directory");
WORDS.put("grep", "search text for a pattern");
WORDS.put("chmod", "change file permissions");
WORDS.put("chown", "change file owner");
WORDS.put("mkdir", "make a directory");
WORDS.put("tail", "show the end of a file");
WORDS.put("systemctl", "control system services");
WORDS.put("journalctl", "read the system journal");
WORDS.put("rsync", "copy files efficiently");
WORDS.put("ssh", "log in to another machine");
WORDS.put("crontab", "schedule repeating jobs");
WORDS.put("kill", "stop a process");
WORDS.put("df", "show free disk space");
}
static final String[] STAGES = {
"\n\n\n\n\n_______",
"\n |\n |\n |\n |\n_|_____",
" ______\n |\n |\n |\n |\n_|_____",
" ______\n | |\n | O\n |\n |\n_|_____",
" ______\n | |\n | O\n | |\n |\n_|_____",
" ______\n | |\n | O\n | /|\\\n |\n_|_____",
" ______\n | |\n | O\n | /|\\\n | / \\\n_|_____"
};
static void play(Scanner in, Random random) {
List<String> names = new ArrayList<>(WORDS.keySet());
String word = names.get(random.nextInt(names.size()));
String hint = WORDS.get(word);
Set<Character> guessed = new HashSet<>();
int misses = 0;
while (misses < STAGES.length - 1) {
System.out.println(STAGES[misses]);
StringBuilder shown = new StringBuilder();
boolean complete = true;
for (char c : word.toCharArray()) {
if (guessed.contains(c)) {
shown.append(c).append(' ');
} else {
shown.append("_ ");
complete = false;
}
}
System.out.println("\nCommand: " + shown.toString().trim());
System.out.println("Hint: " + hint);
if (complete) {
System.out.println("\nYou got it! The command was: " + word);
return;
}
System.out.print("Guess a letter: ");
String letter = in.nextLine().trim().toLowerCase();
if (letter.length() != 1 || !Character.isLetter(letter.charAt(0))) {
System.out.println("Type a single letter.");
} else if (guessed.contains(letter.charAt(0))) {
System.out.println("You already tried that one.");
} else if (word.contains(letter)) {
guessed.add(letter.charAt(0));
} else {
guessed.add(letter.charAt(0));
misses++;
System.out.println("Nope!");
}
}
System.out.println(STAGES[misses]);
System.out.println("\nGame over. The command was: " + word);
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Random random = new Random();
String seed = System.getenv("HANGSEED");
if (seed != null) {
random = new Random(Long.parseLong(seed));
}
System.out.println("=== cmd-hangman by TechCommune (Java) ===");
while (true) {
play(in, random);
System.out.print("\nPlay again? (y/n) ");
if (!in.nextLine().trim().toLowerCase().equals("y")) {
System.out.println("Happy hacking!");
break;
}
}
}
}
It is 94 lines, and you wrote all of them. Try the bad input first (a number, two letters, a repeated letter), then win and answer n:
$ java Hangman.java
=== cmd-hangman by TechCommune (Java) ===
_______
Command: _ _ _ _ _
Hint: change file permissions
Guess a letter: 7
Type a single letter.
_______
Command: _ _ _ _ _
Hint: change file permissions
Guess a letter: cc
Type a single letter.
_______
Command: _ _ _ _ _
Hint: change file permissions
Guess a letter: c
_______
Command: c _ _ _ _
Hint: change file permissions
Guess a letter: c
You already tried that one.
_______
Command: c _ _ _ _
Hint: change file permissions
Guess a letter: h
_______
Command: c h _ _ _
Hint: change file permissions
Guess a letter: m
_______
Command: c h m _ _
Hint: change file permissions
Guess a letter: o
_______
Command: c h m o _
Hint: change file permissions
Guess a letter: d
_______
Command: c h m o d
Hint: change file permissions
You got it! The command was: chmod
Play again? (y/n) n
Happy hacking!
Compile it and make a jar
Running from source is convenient, but real programs are compiled once and run many times. javac turns Hangman.java into Hangman.class, which holds bytecode that any Java runtime can run:
$ javac Hangman.java
$ ls -l Hangman.class
-rw-r--r-- 1 tc tc 4706 Oct 31 03:33 Hangman.class
$ sh -c 'printf "c\nh\nm\no\nd\nn\n" | java Hangman | tail -4'
You got it! The command was: chmod
Play again? (y/n) Happy hacking!
java Hangman (no .java, no .class) runs the compiled class. To hand the program to someone else, put it into a jar, a zip file with the classes and a note about which class starts the program. The e in cfe stands for entry point:
$ jar --create --file hangman.jar --main-class Hangman Hangman.class
$ jar tf hangman.jar
META-INF/
META-INF/MANIFEST.MF
Hangman.class
$ ls -l hangman.jar
-rw-r--r-- 1 tc tc 3110 Oct 31 03:33 hangman.jar
A jar runs with java -jar. The play-again question comes last; here it is answered by a pipe so that the example is repeatable:
$ sh -c 'printf "c\nh\nm\no\nd\nn\n" | java -jar hangman.jar | tail -4'
You got it! The command was: chmod
Play again? (y/n) Happy hacking!
Play it for real with java -jar hangman.jar. This jar is what the next guide packages as a .deb and an .rpm.
When javac complains
javac names the file, the line and the problem, and shows the line with a ^ under the place. The five messages you are most likely to meet, each produced on purpose with a tiny file:
A missing semicolon:
$ cat Bad1.java
public class Bad1 {
public static void main(String[] args) {
System.out.println("hello")
}
}
$ javac Bad1.java
Bad1.java:3: error: ';' expected
System.out.println("hello")
^
1 error
A name that does not exist (here a variable was not declared):
$ cat Bad2.java
public class Bad2 {
public static void main(String[] args) {
System.out.println(greeting);
}
}
$ javac Bad2.java
Bad2.java:3: error: cannot find symbol
System.out.println(greeting);
^
symbol: variable greeting
location: class Bad2
1 error
A class whose name does not match its file:
$ cat Bad3.java
public class Hello {
public static void main(String[] args) {
System.out.println("hello");
}
}
$ javac Bad3.java
Bad3.java:1: error: class Hello is public, should be declared in a file named Hello.java
public class Hello {
^
1 error
A missing closing brace:
$ cat Bad4.java
public class Bad4 {
public static void main(String[] args) {
System.out.println("hello");
}
$ javac Bad4.java
Bad4.java:4: error: reached end of file while parsing
}
^
1 error
Text where a number is expected:
$ cat Bad5.java
public class Bad5 {
public static void main(String[] args) {
int n = "five";
}
}
$ javac Bad5.java
Bad5.java:3: error: incompatible types: String cannot be converted to int
int n = "five";
^
1 error
The rule: read the first message first, because later ones are often a consequence of it, and look at the line above the one it points to for a missing semicolon or brace.
Make it yours
The commands are the lines of the static block. Add a line (the same shape, a command in lower case and a short hint):
WORDS.put("tar", "bundle files into an archive");
WORDS.put("sudo", "run a command as another user");
Use only letters a to z in the command, because the game compares single letters. To make the game harder or easier, change the STAGES array: fewer pictures end the game sooner. After any change, compile and play one round before you do anything else:
$ javac Hangman.java && java Hangman
Ideas: count the wins and print them after each round; a second map for another topic, with a menu to choose; a word list read from a file (java.nio.file.Files.readAllLines) so that the game can change without being compiled again.
The finished file, to compare
If something does not work, compare your file with the real one by its checksum:
$ sha256sum Hangman.java
b10b61c6d145d6be5bba4228833abf2bd9761f53724338ad8c7422f9c0dc7ae0 Hangman.java
$ wc -l Hangman.java
94 Hangman.java
Same line count and checksum mean identical text. A different checksum with the same line count usually means a stray character or a different quote: print the file with cat -A to see tabs and trailing spaces.
Next in the series: Package a Java App as a .deb and an .rpm, Side by Side. It builds this hangman.jar into an Ubuntu package and a Rocky Linux package, and shows what is different about packaging a program that needs a Java runtime.