A side-by-side manual: build one jar that runs on Java 17 and 21, package it as a .deb and an .rpm, see the runtime installed by apt and dnf on machines with no Java, compare what the tools say, and learn how removal differs.
▶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.
dpkg-deb --root-owner-group --build cmd-hangman-java_1.0.0_all
rpmbuild -ba ~/rpmbuild/SPECS/cmd-hangman-java.spec
sudo apt install ./cmd-hangman-java_1.0.0_all.deb | sudo dnf install ./cmd-hangman-java-1.0.0-1.el9.noarch.rpm
The Python game in the earlier packaging guides needed an interpreter that every machine already has. A Java program needs a runtime that many machines do not have, so packaging it shows the part of packaging that matters most: dependencies. This guide builds the Java hangman jar (from the guide Java Game: Build Linux Command Hangman in Java, Type Along) into a .deb on Ubuntu and an .rpm on Rocky Linux, side by side, and installs both on machines with no Java at all.
You will see the version trap that catches nearly everyone who builds a jar on one machine and runs it on another, how the same dependency is written in each format, how rpmbuild compiles the program while dpkg-deb only packs files, and how apt and dnf differ when you remove the package. It is also the comparison of the two formats: a table of every task in both, at the end.
Every example ran for real on Ubuntu 24.04 (OpenJDK 21, dpkg 1.22.6, lintian 2.117.0) and Rocky Linux 9.8 (OpenJDK 17, rpm 4.16.1.3, rpmlint 1.11), as an ordinary user, with a second clean machine of each for the installs. The word in the game examples is fixed with the HANGSEED variable.
What changes when the program is Java
The Python game in the first guides needed nothing but a text file and an interpreter that every machine has. A Java program changes four things about the package, and this guide goes through each of them for a .deb on Ubuntu and an .rpm on Rocky Linux, side by side:
the runtime the program needs a Java runtime (JRE) to run. The package must say so, so that apt and dnf install one on a machine that has none. This is the first time the dependency is a big one
the files the program is one jar file. Linux puts jars in /usr/share/java, and the package adds a one-line launcher script in /usr/bin so that you can start it by name
the build a .deb is made from files you have already built, but an .rpm is made by running the build, so the spec file calls the compiler. The tools for it are build requirements, not runtime requirements
the version Java bytecode records the version of the compiler. A jar built with a newer JDK than the runtime that runs it fails. You will see that error, and the one option that prevents it
The program is Hangman.java from the Java guide in the series (Java Game: Build Linux Command Hangman in Java, Type Along). It is architecture-independent bytecode, so both packages are "all" (deb) and "noarch" (rpm).
The labs were an Ubuntu 24.04 machine with OpenJDK 21 and a Rocky Linux 9.8 machine with OpenJDK 17, and a second, clean machine of each without any Java:
$ cat /etc/os-release | grep PRETTY_NAME
PRETTY_NAME="Ubuntu 24.04.5 LTS"
$ java -version 2>&1 | head -1
openjdk version "21.0.12.1" 2026-08-18
$ cat /etc/rocky-release
Rocky Linux release 9.8 (Blue Onyx)
$ java -version 2>&1 | head -1
openjdk version "17.0.21" 2026-10-20 LTS
One jar for both: the version trap
Build the jar on the Ubuntu machine, the way the Java guide did, and see which Java version it was built for. javap is the class-file inspector that comes with the JDK; "major version" 65 is Java 21, 61 is Java 17:
$ ls
Hangman.java
$ javac Hangman.java
$ javap -v Hangman.class | grep "major version"
major version: 65
$ jar --create --file hangman.jar --main-class Hangman Hangman.class
Take that jar to the Rocky machine, which has Java 17, and run it:
$ java -version 2>&1 | head -1
openjdk version "17.0.21" 2026-10-20 LTS
$ java -jar hangman.jar </dev/null 2>&1 | head -4
Error: LinkageError occurred while loading main class Hangman
java.lang.UnsupportedClassVersionError: Hangman has been compiled by a more recent version of the Java Runtime (class file version 65.0), this version of the Java Runtime only recognizes class file versions up to 61.0
The message is long, but the part to read is "class file version 65.0" against "up to 61.0": the jar was built for Java 21 and the runtime knows Java 17. The fix is to tell the compiler which version to produce. --release 17 builds for Java 17 and checks the code against the Java 17 library, so it also runs on 21:
$ javac --release 17 Hangman.java
$ javap -v Hangman.class | grep "major version"
major version: 61
$ jar --create --file hangman.jar --main-class Hangman Hangman.class
$ sh -c 'printf "c\nh\nm\no\nd\nn\n" | java -jar hangman.jar | tail -3'
You got it! The command was: chmod
Play again? (y/n) Happy hacking!
$ sh -c 'printf "c\nh\nm\no\nd\nn\n" | java -jar hangman.jar | tail -3'
You got it! The command was: chmod
Play again? (y/n) Happy hacking!
The same jar now runs on both. The packages below say "Java 17 or newer" because that is what the jar needs. (The word is fixed with HANGSEED in these examples.)
The launcher script
A jar is started with java -jar PATH. Nobody wants to type that, and the path may change, so the package ships a small script, in /usr/bin, that does it. exec replaces the shell with Java, so the script leaves nothing behind, and "$@" passes any arguments on:
$ cat cmd-hangman-java
#!/bin/sh
exec java -jar /usr/share/java/cmd-hangman-java.jar "$@"
$ chmod 755 cmd-hangman-java
$ ls -l cmd-hangman-java
-rwxr-xr-x 1 tc tc 67 Oct 31 03:48 cmd-hangman-java
The jar will be installed as /usr/share/java/cmd-hangman-java.jar in both packages. The package name is cmd-hangman-java, so it can be installed next to the Python version (cmd-hangman) without any file in common.
The .deb: folder, control file, documents
A .deb is built from a folder laid out like the target system, the same as in the Python guide. Put the jar and the launcher in place, and write the control file. Debian's rule for jars is a versioned file name (cmd-hangman-java-1.0.0.jar) plus an unversioned symbolic link (cmd-hangman-java.jar) that programs use; lintian warns with bad-jar-name about a jar without a version in its name. The launcher uses the link, so it does not change when the version does. The difference from the Python control file is one line, Depends. default-jre-headless is the Ubuntu name for "the standard Java runtime without graphics"; the alternative after the bar, java17-runtime-headless, is what any package providing Java 17 offers, so a machine that already has a suitable Java is not forced to install another:
$ install -D -m755 cmd-hangman-java cmd-hangman-java_1.0.0_all/usr/bin/cmd-hangman-java
$ install -D -m644 hangman.jar cmd-hangman-java_1.0.0_all/usr/share/java/cmd-hangman-java-1.0.0.jar
$ ln -s cmd-hangman-java-1.0.0.jar cmd-hangman-java_1.0.0_all/usr/share/java/cmd-hangman-java.jar
$ mkdir -p cmd-hangman-java_1.0.0_all/DEBIAN cmd-hangman-java_1.0.0_all/usr/share/doc/cmd-hangman-java cmd-hangman-java_1.0.0_all/usr/share/man/man1
$ cat cmd-hangman-java_1.0.0_all/DEBIAN/control
Package: cmd-hangman-java
Version: 1.0.0
Section: utils
Priority: optional
Architecture: all
Depends: default-jre-headless | java17-runtime-headless
Maintainer: TechCommune <admin@techcommune.org>
Description: Linux command hangman for the terminal, in Java
Guess the Linux command one letter at a time before the
stick figure is finished. The Java version of the game from
the packaging series on techcommune.org.
The copyright file, the changelog and the manual page are the same three documents as in the Python .deb guide, with the new name. The changelog and the man page are compressed:
$ cat cmd-hangman-java_1.0.0_all/usr/share/doc/cmd-hangman-java/copyright
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: cmd-hangman-java
Source: https://techcommune.org
Files: *
Copyright: 2026 TechCommune
License: CC0-1.0
On Debian systems, the complete text of the CC0 1.0 Universal
license can be found in /usr/share/common-licenses/CC0-1.0.
$ cat changelog-tmp
cmd-hangman-java (1.0.0) unstable; urgency=low
* First release.
-- TechCommune <admin@techcommune.org> Fri, 30 Oct 2026 12:00:00 +0000
$ cat cmd-hangman-java.1
.TH CMD-HANGMAN-JAVA 1 "October 2026" "cmd-hangman-java 1.0.0" "TechCommune"
.SH NAME
cmd-hangman-java \- guess the Linux command before the stick figure is finished
.SH SYNOPSIS
.B cmd-hangman-java
.SH DESCRIPTION
.B cmd-hangman-java
picks a Linux command at random, shows a hint and one underscore per letter.
Type one letter at a time. Six wrong letters finish the stick figure and end
the round. The program needs a Java runtime, version 17 or newer.
.SH AUTHOR
TechCommune, https://techcommune.org
$ gzip -9n -c changelog-tmp > cmd-hangman-java_1.0.0_all/usr/share/doc/cmd-hangman-java/changelog.gz
$ gzip -9n -c cmd-hangman-java.1 > cmd-hangman-java_1.0.0_all/usr/share/man/man1/cmd-hangman-java.1.gz
$ rm changelog-tmp
$ tree cmd-hangman-java_1.0.0_all
cmd-hangman-java_1.0.0_all
|-- DEBIAN
| `-- control
`-- usr
|-- bin
| `-- cmd-hangman-java
`-- share
|-- doc
| `-- cmd-hangman-java
| |-- changelog.gz
| `-- copyright
|-- java
| |-- cmd-hangman-java-1.0.0.jar
| `-- cmd-hangman-java.jar -> cmd-hangman-java-1.0.0.jar
`-- man
`-- man1
`-- cmd-hangman-java.1.gz
10 directories, 7 files
An .rpm is built from a source tarball by a recipe, so the package contains no prebuilt jar: the build compiles it. Make the source folder, in the usual name-version form, with the program, the launcher and the three documents:
$ rpmdev-setuptree
$ mkdir cmd-hangman-java-1.0.0
$ cp Hangman.java cmd-hangman-java-1.0.0/
$ cat cmd-hangman-java-1.0.0/cmd-hangman-java
#!/bin/sh
exec java -jar /usr/share/java/cmd-hangman-java.jar "$@"
$ cat cmd-hangman-java-1.0.0/README.md
# cmd-hangman-java
Guess the Linux command, one letter at a time, before the stick figure is
finished. The Java version of the tutorial game from https://techcommune.org
Run: cmd-hangman-java
$ cat cmd-hangman-java-1.0.0/LICENSE
CC0 1.0 Universal - Public Domain Dedication
The person who associated this work with this deed has dedicated the work
to the public domain by waiving all of his or her rights to the work
worldwide under copyright law, including all related and neighbouring
rights, to the extent allowed by law. You can copy, modify, distribute and
perform the work, even for commercial purposes, all without asking
permission. Full text: https://creativecommons.org/publicdomain/zero/1.0/
$ cat cmd-hangman-java-1.0.0/cmd-hangman-java.1
.TH CMD-HANGMAN-JAVA 1 "October 2026" "cmd-hangman-java 1.0.0" "TechCommune"
.SH NAME
cmd-hangman-java \- guess the Linux command before the stick figure is finished
.SH SYNOPSIS
.B cmd-hangman-java
.SH DESCRIPTION
.B cmd-hangman-java
picks a Linux command at random, shows a hint and one underscore per letter.
Type one letter at a time. Six wrong letters finish the stick figure and end
the round. The program needs a Java runtime, version 17 or newer.
.SH AUTHOR
TechCommune, https://techcommune.org
$ tar -czf ~/rpmbuild/SOURCES/cmd-hangman-java-1.0.0.tar.gz cmd-hangman-java-1.0.0
$ tar -tzf ~/rpmbuild/SOURCES/cmd-hangman-java-1.0.0.tar.gz
cmd-hangman-java-1.0.0/
cmd-hangman-java-1.0.0/cmd-hangman-java.1
cmd-hangman-java-1.0.0/LICENSE
cmd-hangman-java-1.0.0/README.md
cmd-hangman-java-1.0.0/Hangman.java
cmd-hangman-java-1.0.0/cmd-hangman-java
Now the spec file. Compared with the Python spec there are four differences. BuildRequires names what the build needs (the JDK; rpmbuild refuses to start without it). Requires: java-headless is the runtime dependency: every Java runtime package provides that name. %build now does real work: it compiles for Java 17 and makes the jar, so the package always contains a jar built for the right version. And %install puts the jar in %{_javadir}, which is /usr/share/java:
$ cat ~/rpmbuild/SPECS/cmd-hangman-java.spec
Name: cmd-hangman-java
Version: 1.0.0
Release: 1%{?dist}
Summary: Linux command hangman for the terminal, in Java
License: CC0
URL: https://techcommune.org
Source0: %{name}-%{version}.tar.gz
BuildArch: noarch
BuildRequires: java-17-openjdk-devel
Requires: java-headless
%description
Guess the Linux command one letter at a time before the
stick figure is finished. The Java version of the game from
the packaging series. Needs a Java runtime, version 17 or newer.
%prep
%setup -q
%build
javac --release 17 Hangman.java
jar --create --file hangman.jar --main-class Hangman Hangman.class
%install
install -D -m 0644 hangman.jar %{buildroot}%{_javadir}/%{name}.jar
install -D -m 0755 cmd-hangman-java %{buildroot}%{_bindir}/cmd-hangman-java
install -D -m 0644 cmd-hangman-java.1 %{buildroot}%{_mandir}/man1/cmd-hangman-java.1
%files
%license LICENSE
%doc README.md
%{_javadir}/%{name}.jar
%{_bindir}/cmd-hangman-java
%{_mandir}/man1/cmd-hangman-java.1*
%changelog
* Fri Oct 30 2026 TechCommune <admin@techcommune.org> - 1.0.0-1
- First package of the Java version
$ rpm --eval '%{_javadir}'
/usr/share/java
$ rpmspec -q --qf '%{name}-%{version}-%{release}.%{arch}\n' ~/rpmbuild/SPECS/cmd-hangman-java.spec
cmd-hangman-java-1.0.0-1.el9.noarch
The .rpm: build and check
Build as an ordinary user. The lines that matter are the compile, the jar and the files written:
$ rpmbuild -ba ~/rpmbuild/SPECS/cmd-hangman-java.spec 2>&1 | grep -E "^(\+ javac|\+ jar|Wrote|error)"
+ javac --release 17 Hangman.java
+ jar --create --file hangman.jar --main-class Hangman Hangman.class
Wrote: /home/tc/rpmbuild/SRPMS/cmd-hangman-java-1.0.0-1.el9.src.rpm
Wrote: /home/tc/rpmbuild/RPMS/noarch/cmd-hangman-java-1.0.0-1.el9.noarch.rpm
$ rpmlint ~/rpmbuild/SPECS/cmd-hangman-java.spec ~/rpmbuild/RPMS/noarch/cmd-hangman-java-1.0.0-1.el9.noarch.rpm
/home/tc/rpmbuild/SPECS/cmd-hangman-java.spec: W: invalid-url Source0: cmd-hangman-java-1.0.0.tar.gz
1 packages and 1 specfiles checked; 0 errors, 1 warnings.
$ rpm -qplv ~/rpmbuild/RPMS/noarch/cmd-hangman-java-1.0.0-1.el9.noarch.rpm
-rwxr-xr-x 1 root root 71 Oct 31 03:49 /usr/bin/cmd-hangman-java
drwxr-xr-x 2 root root 0 Oct 31 03:49 /usr/share/doc/cmd-hangman-java
-rw-r--r-- 1 root root 194 Oct 31 03:49 /usr/share/doc/cmd-hangman-java/README.md
-rw-r--r-- 1 root root 3131 Oct 31 03:49 /usr/share/java/cmd-hangman-java.jar
drwxr-xr-x 2 root root 0 Oct 31 03:49 /usr/share/licenses/cmd-hangman-java
-rw-r--r-- 1 root root 475 Oct 31 03:49 /usr/share/licenses/cmd-hangman-java/LICENSE
-rw-r--r-- 1 root root 325 Oct 31 03:49 /usr/share/man/man1/cmd-hangman-java.1.gz
$ rpm -qpR ~/rpmbuild/RPMS/noarch/cmd-hangman-java-1.0.0-1.el9.noarch.rpm
/usr/bin/sh
java-headless
rpmlib(CompressedFileNames) <= 3.0.4-1
rpmlib(FileDigests) <= 4.6.0-1
rpmlib(PayloadFilesHavePrefix) <= 4.0-1
rpmlib(PayloadIsZstd) <= 5.4.18-1
If the JDK were missing, the build would stop at once, which is what BuildRequires is for:
$ sudo dnf -y remove java-17-openjdk-devel 2>&1 | tail -1
Complete!
$ rpmbuild -ba ~/rpmbuild/SPECS/cmd-hangman-java.spec 2>&1 | tail -3
setting SOURCE_DATE_EPOCH=1793318400
error: Failed build dependencies:
java-17-openjdk-devel is needed by cmd-hangman-java-1.0.0-1.el9.noarch
$ sudo dnf -y install java-17-openjdk-devel 2>&1 | tail -1
Complete!
Install on a machine with no Java
The real test is a clean machine. Both of these have no Java at all. Copy the packages over (scp in real life) and install them. The point of the Depends and Requires lines is that a runtime appears by itself:
$ command -v java cmd-hangman-java || echo "no java, no game"
no java, no game
$ sudo apt-get update -qq
$ sudo apt-get install -y ./cmd-hangman-java_1.0.0_all.deb 2>&1 | sed -n '/NEW packages/,/^[0-9]* upgraded/p'
The following NEW packages will be installed:
adduser alsa-topology-conf alsa-ucm-conf ca-certificates
ca-certificates-java cmd-hangman-java dbus dbus-bin dbus-daemon
dbus-session-bus-common dbus-system-bus-common default-jre-headless
fontconfig-config fonts-dejavu-core fonts-dejavu-mono java-common
krb5-locales libasound2-data libasound2t64 libavahi-client3
libavahi-common-data libavahi-common3 libbrotli1 libcups2t64 libdbus-1-3
libexpat1 libfontconfig1 libfreetype6 libglib2.0-0t64 libglib2.0-data
libgraphite2-3 libgssapi-krb5-2 libharfbuzz0b libicu74 libjpeg-turbo8
libjpeg8 libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 liblcms2-2
libnspr4 libnss3 libpcsclite1 libpng16-16t64 libsqlite3-0 libxml2
openjdk-21-jre-headless openssl shared-mime-info xdg-user-dirs
The following packages will be upgraded:
libssl3t64
1 upgraded, 51 newly installed, 0 to remove and 6 not upgraded.
$ command -v java cmd-hangman-java
/usr/bin/java
/usr/bin/cmd-hangman-java
$ java -version 2>&1 | head -1
openjdk version "21.0.12.1" 2026-08-18
$ command -v java cmd-hangman-java || echo "no java, no game"
no java, no game
$ sudo dnf -y install ./cmd-hangman-java-1.0.0-1.el9.noarch.rpm 2>&1 | sed -n '/Installing dependencies/,/Transaction Summary/p'
Installing dependencies:
alsa-lib x86_64 1.2.15.3-1.el9 appstream 525 k
avahi-libs x86_64 0.8-23.el9 baseos 66 k
copy-jdk-configs noarch 4.0-3.el9.0.1 appstream 27 k
cups-libs x86_64 1:2.3.3op2-39.el9_8 baseos 260 k
dbus-libs x86_64 1:1.12.20-8.el9 baseos 151 k
java-17-openjdk-headless x86_64 1:17.0.21.0.9-2.2.el9_8 appstream 44 M
javapackages-filesystem noarch 6.4.0-1.el9 appstream 10 k
lksctp-tools x86_64 1.0.19-3.el9_4 baseos 96 k
lua x86_64 5.4.4-4.el9 appstream 187 k
lua-posix x86_64 35.0-8.el9 appstream 131 k
nspr x86_64 4.39.0-5.el9_8 appstream 134 k
nss x86_64 3.124.0-5.el9_8 appstream 726 k
nss-softokn x86_64 3.124.0-5.el9_8 appstream 411 k
nss-softokn-freebl x86_64 3.124.0-5.el9_8 appstream 381 k
nss-sysinit x86_64 3.124.0-5.el9_8 appstream 18 k
nss-util x86_64 3.124.0-5.el9_8 appstream 90 k
tzdata-java noarch 2026e-1.el9_8 appstream 145 k
Transaction Summary
$ command -v java cmd-hangman-java
/usr/bin/java
/usr/bin/cmd-hangman-java
$ java -version 2>&1 | head -1
openjdk version "17.0.21" 2026-10-20 LTS
apt and dnf each chose a runtime from what the dependency line allows, installed it, and then installed the game. Both ended up with a working java. Now play (the word is fixed in these examples):
$ cmd-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: 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!
$ cmd-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: 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!
Ask each system what it installed
The same four questions, in the two tools: what does the package need, what files does it own, which package owns this file, and what is installed. The output is for the Ubuntu machine first, then Rocky:
$ dpkg -s cmd-hangman-java | grep -E "^(Version|Depends)"
Version: 1.0.0
Depends: default-jre-headless | java17-runtime-headless
$ dpkg -L cmd-hangman-java | grep -v -E "^/(usr|usr/share|usr/share/doc)?$"
/.
/usr/bin
/usr/bin/cmd-hangman-java
/usr/share/doc/cmd-hangman-java
/usr/share/doc/cmd-hangman-java/changelog.gz
/usr/share/doc/cmd-hangman-java/copyright
/usr/share/java
/usr/share/java/cmd-hangman-java-1.0.0.jar
/usr/share/man
/usr/share/man/man1
/usr/share/man/man1/cmd-hangman-java.1.gz
/usr/share/java/cmd-hangman-java.jar
$ dpkg -S /usr/share/java/cmd-hangman-java.jar
cmd-hangman-java: /usr/share/java/cmd-hangman-java.jar
$ dpkg -l | grep -E "openjdk|cmd-hangman" | cut -c1-80
ii cmd-hangman-java 1.0.0 all
ii openjdk-21-jre-headless:amd64 21.0.12.1+1-1~24.04.4 amd64
$ rpm -q --requires cmd-hangman-java | grep -v rpmlib
/usr/bin/sh
java-headless
$ rpm -ql cmd-hangman-java
/usr/bin/cmd-hangman-java
/usr/share/doc/cmd-hangman-java
/usr/share/doc/cmd-hangman-java/README.md
/usr/share/java/cmd-hangman-java.jar
/usr/share/licenses/cmd-hangman-java
/usr/share/licenses/cmd-hangman-java/LICENSE
/usr/share/man/man1/cmd-hangman-java.1.gz
$ rpm -qf /usr/share/java/cmd-hangman-java.jar
cmd-hangman-java-1.0.0-1.el9.noarch
$ rpm -qa | grep -E "openjdk|cmd-hangman" | sort
cmd-hangman-java-1.0.0-1.el9.noarch
java-17-openjdk-headless-17.0.21.0.9-2.2.el9_8.x86_64
Notice what the Rocky runtime really is: dnf picked a java-headless provider, and the package list shows which one. If you need exactly one Java version, say so in the Requires line (java-17-openjdk-headless) instead of the generic name.
Remove: who cleans up the runtime
Removing the game raises one question: what about the Java runtime that was installed for it? The two tools answer differently. apt leaves it, marked as "installed automatically", and tells you; you remove such leftovers with autoremove. dnf removes the game and, in the same transaction, the dependencies that nothing else needs:
$ sudo apt-get remove -y cmd-hangman-java 2>&1 | grep -E "^Removing|no longer required"
The following packages were automatically installed and are no longer required:
Removing cmd-hangman-java (1.0.0) ...
$ command -v cmd-hangman-java || echo "game removed"
game removed
$ command -v java
/usr/bin/java
$ sudo apt-get autoremove -y 2>&1 | grep -E "^[0-9]+ upgraded"
0 upgraded, 0 newly installed, 47 to remove and 6 not upgraded.
$ command -v java || echo "java removed"
java removed
$ sudo dnf -y remove cmd-hangman-java 2>&1 | grep -E "^Remove|java-17|cmd-hangman|^Removed" | head -6
cmd-hangman-java noarch 1.0.0-1.el9 @@commandline 4.1 k
java-17-openjdk-headless x86_64 1:17.0.21.0.9-2.2.el9_8 @appstream 186 M
Remove 18 Packages
Erasing : cmd-hangman-java-1.0.0-1.el9.noarch 1/18
Erasing : java-17-openjdk-headless-1:17.0.21.0.9-2.2.el9_8.x 2/18
Running scriptlet: java-17-openjdk-headless-1:17.0.21.0.9-2.2.el9_8.x 2/18
$ command -v cmd-hangman-java || echo "game removed"
game removed
$ command -v java || echo "java removed"
java removed
$ rpm -q java-17-openjdk-headless
package java-17-openjdk-headless is not installed
(exit status 1)
In the Ubuntu run, 47 packages came out with autoremove; on Rocky the runtime went with the game, because dnf's clean_requirements_on_remove setting is on by default. Both are safe because the machine was a test machine. On a machine that matters, read the list before you say yes: another program may use that Java by accident, and a dependency you installed by hand yourself is never removed automatically (apt-mark manual NAME and dnf mark install NAME record that).
Side by side: .deb and .rpm for a Java program
What you did, task by task, in both formats:
task .deb (Ubuntu, Debian) .rpm (Rocky, RHEL, Fedora)
runtime dependency Depends: default-jre-headless | Requires: java-headless
java17-runtime-headless
build dependency none (the jar is built before) BuildRequires: java-17-openjdk-devel
who compiles you, before dpkg-deb rpmbuild, in %build
build for the right Java javac --release 17 javac --release 17 in %build
where the jar goes usr/share/java/NAME-1.0.0.jar + link %{_javadir}/NAME.jar (/usr/share/java)
launcher script usr/bin/ in the folder install -D ... %{_bindir}/...
architecture Architecture: all BuildArch: noarch
build command dpkg-deb --root-owner-group --build DIR rpmbuild -ba FILE.spec
checker lintian FILE.deb rpmlint FILE.rpm
install a local file apt install ./FILE.deb dnf install ./FILE.rpm
what does it need dpkg -s NAME | grep Depends rpm -q --requires NAME
files of a package dpkg -L NAME rpm -ql NAME
owner of a file dpkg -S PATH rpm -qf PATH
remove, then the leftovers apt remove NAME; apt autoremove dnf remove NAME (takes unneeded deps along)
The one idea behind the whole table: a package is files plus a description of what must be on the machine for them to work. For a script that was an interpreter; for a Java program it is a runtime, and the package manager finds it, installs it, and tracks it so that it can be cleaned up later.
Checklist for packaging a Java program
Before you hand the package to anyone:
1. Build the jar with --release N, where N is the oldest Java you want to support, and say "N or newer" in the dependency.
2. Put the jar in /usr/share/java (.deb: a versioned name and an unversioned link; .rpm: the plain name) and start it from a small launcher in /usr/bin.
3. Depends / Requires names a runtime in its "headless" form unless the program has a window.
4. The jar is architecture-independent: "all" and "noarch", not amd64 or x86_64.
5. The .rpm lists the JDK in BuildRequires; the .deb needs no build dependency because the jar already exists.
6. Run lintian and rpmlint, and install on a machine with no Java to prove that the dependency pulls one in.
7. After testing, remove the package and run autoremove to see that nothing was left behind.
The series: Python Game: Build Linux Command Hangman, Build a .deb Package, Build an .rpm Package, Java Game: Build Linux Command Hangman in Java, and this guide. To go further: a program with dependencies of its own (other jars: use the distributions' own java packages), or a launcher with options such as -Xmx for the memory the program may use.