Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
After install, both live side by side under /usr/lib/jvm/. The package does not change which java is the default. That is handled by the alternatives system in Step 6.
Confirm the 25 binary runs:
/usr/lib/jvm/java-25-openjdk-amd64/bin/java --version
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
After install, both live side by side under /usr/lib/jvm/. The package does not change which java is the default. That is handled by the alternatives system in Step 6.
Confirm the 25 binary runs:
/usr/lib/jvm/java-25-openjdk-amd64/bin/java --version
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
OpenJDK 25 is the newer LTS line and Debian 13 security-backports it alongside 21. Useful if your workload targets the newest LTS or you are evaluating the switch:
sudo apt install -y openjdk-25-jdk
After install, both live side by side under /usr/lib/jvm/. The package does not change which java is the default. That is handled by the alternatives system in Step 6.
Confirm the 25 binary runs:
/usr/lib/jvm/java-25-openjdk-amd64/bin/java --version
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
The binary lives at /usr/lib/jvm/java-21-openjdk-amd64/. That path matters in a moment when you set JAVA_HOME.
Debian 12 difference: On bookworm, default-jdk resolves to openjdk-17-jdk. OpenJDK 21 is available in bookworm-backports:
# Debian 12 only, enable backports first
echo "deb http://deb.debian.org/debian bookworm-backports main" \
| sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update
sudo apt install -y -t bookworm-backports openjdk-21-jdk
Step 3: Install OpenJDK 25 for the latest LTS
OpenJDK 25 is the newer LTS line and Debian 13 security-backports it alongside 21. Useful if your workload targets the newest LTS or you are evaluating the switch:
sudo apt install -y openjdk-25-jdk
After install, both live side by side under /usr/lib/jvm/. The package does not change which java is the default. That is handled by the alternatives system in Step 6.
Confirm the 25 binary runs:
/usr/lib/jvm/java-25-openjdk-amd64/bin/java --version
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
The binary lives at /usr/lib/jvm/java-21-openjdk-amd64/. That path matters in a moment when you set JAVA_HOME.
Debian 12 difference: On bookworm, default-jdk resolves to openjdk-17-jdk. OpenJDK 21 is available in bookworm-backports:
# Debian 12 only, enable backports first
echo "deb http://deb.debian.org/debian bookworm-backports main" \
| sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update
sudo apt install -y -t bookworm-backports openjdk-21-jdk
Step 3: Install OpenJDK 25 for the latest LTS
OpenJDK 25 is the newer LTS line and Debian 13 security-backports it alongside 21. Useful if your workload targets the newest LTS or you are evaluating the switch:
sudo apt install -y openjdk-25-jdk
After install, both live side by side under /usr/lib/jvm/. The package does not change which java is the default. That is handled by the alternatives system in Step 6.
Confirm the 25 binary runs:
/usr/lib/jvm/java-25-openjdk-amd64/bin/java --version
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Real output from the test VM:
openjdk 21.0.10 2026-01-20
OpenJDK Runtime Environment (build 21.0.10+7-Debian-1deb13u1)
OpenJDK 64-Bit Server VM (build 21.0.10+7-Debian-1deb13u1, mixed mode, sharing)
javac 21.0.10
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
The binary lives at /usr/lib/jvm/java-21-openjdk-amd64/. That path matters in a moment when you set JAVA_HOME.
Debian 12 difference: On bookworm, default-jdk resolves to openjdk-17-jdk. OpenJDK 21 is available in bookworm-backports:
# Debian 12 only, enable backports first
echo "deb http://deb.debian.org/debian bookworm-backports main" \
| sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update
sudo apt install -y -t bookworm-backports openjdk-21-jdk
Step 3: Install OpenJDK 25 for the latest LTS
OpenJDK 25 is the newer LTS line and Debian 13 security-backports it alongside 21. Useful if your workload targets the newest LTS or you are evaluating the switch:
sudo apt install -y openjdk-25-jdk
After install, both live side by side under /usr/lib/jvm/. The package does not change which java is the default. That is handled by the alternatives system in Step 6.
Confirm the 25 binary runs:
/usr/lib/jvm/java-25-openjdk-amd64/bin/java --version
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
The default-jdk meta-package pulls in whatever the Debian release considers the default (OpenJDK 21 on trixie, OpenJDK 17 on bookworm). Adding openjdk-21-jdk explicitly pins the version so you do not get surprised when the meta-package’s preferred version shifts in a future release.
Verify:
java --version
javac --version
readlink -f $(which java)
Real output from the test VM:
openjdk 21.0.10 2026-01-20
OpenJDK Runtime Environment (build 21.0.10+7-Debian-1deb13u1)
OpenJDK 64-Bit Server VM (build 21.0.10+7-Debian-1deb13u1, mixed mode, sharing)
javac 21.0.10
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
The binary lives at /usr/lib/jvm/java-21-openjdk-amd64/. That path matters in a moment when you set JAVA_HOME.
Debian 12 difference: On bookworm, default-jdk resolves to openjdk-17-jdk. OpenJDK 21 is available in bookworm-backports:
# Debian 12 only, enable backports first
echo "deb http://deb.debian.org/debian bookworm-backports main" \
| sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update
sudo apt install -y -t bookworm-backports openjdk-21-jdk
Step 3: Install OpenJDK 25 for the latest LTS
OpenJDK 25 is the newer LTS line and Debian 13 security-backports it alongside 21. Useful if your workload targets the newest LTS or you are evaluating the switch:
sudo apt install -y openjdk-25-jdk
After install, both live side by side under /usr/lib/jvm/. The package does not change which java is the default. That is handled by the alternatives system in Step 6.
Confirm the 25 binary runs:
/usr/lib/jvm/java-25-openjdk-amd64/bin/java --version
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
The default-jdk meta-package pulls in whatever the Debian release considers the default (OpenJDK 21 on trixie, OpenJDK 17 on bookworm). Adding openjdk-21-jdk explicitly pins the version so you do not get surprised when the meta-package’s preferred version shifts in a future release.
Verify:
java --version
javac --version
readlink -f $(which java)
Real output from the test VM:
openjdk 21.0.10 2026-01-20
OpenJDK Runtime Environment (build 21.0.10+7-Debian-1deb13u1)
OpenJDK 64-Bit Server VM (build 21.0.10+7-Debian-1deb13u1, mixed mode, sharing)
javac 21.0.10
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
The binary lives at /usr/lib/jvm/java-21-openjdk-amd64/. That path matters in a moment when you set JAVA_HOME.
Debian 12 difference: On bookworm, default-jdk resolves to openjdk-17-jdk. OpenJDK 21 is available in bookworm-backports:
# Debian 12 only, enable backports first
echo "deb http://deb.debian.org/debian bookworm-backports main" \
| sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update
sudo apt install -y -t bookworm-backports openjdk-21-jdk
Step 3: Install OpenJDK 25 for the latest LTS
OpenJDK 25 is the newer LTS line and Debian 13 security-backports it alongside 21. Useful if your workload targets the newest LTS or you are evaluating the switch:
sudo apt install -y openjdk-25-jdk
After install, both live side by side under /usr/lib/jvm/. The package does not change which java is the default. That is handled by the alternatives system in Step 6.
Confirm the 25 binary runs:
/usr/lib/jvm/java-25-openjdk-amd64/bin/java --version
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
OpenJDK 21 is the current long-term support release and the one most readers want. It ships in Debian 13 main with no extra repo:
sudo apt update
sudo apt install -y default-jdk openjdk-21-jdk
The default-jdk meta-package pulls in whatever the Debian release considers the default (OpenJDK 21 on trixie, OpenJDK 17 on bookworm). Adding openjdk-21-jdk explicitly pins the version so you do not get surprised when the meta-package’s preferred version shifts in a future release.
Verify:
java --version
javac --version
readlink -f $(which java)
Real output from the test VM:
openjdk 21.0.10 2026-01-20
OpenJDK Runtime Environment (build 21.0.10+7-Debian-1deb13u1)
OpenJDK 64-Bit Server VM (build 21.0.10+7-Debian-1deb13u1, mixed mode, sharing)
javac 21.0.10
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
The binary lives at /usr/lib/jvm/java-21-openjdk-amd64/. That path matters in a moment when you set JAVA_HOME.
Debian 12 difference: On bookworm, default-jdk resolves to openjdk-17-jdk. OpenJDK 21 is available in bookworm-backports:
# Debian 12 only, enable backports first
echo "deb http://deb.debian.org/debian bookworm-backports main" \
| sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update
sudo apt install -y -t bookworm-backports openjdk-21-jdk
Step 3: Install OpenJDK 25 for the latest LTS
OpenJDK 25 is the newer LTS line and Debian 13 security-backports it alongside 21. Useful if your workload targets the newest LTS or you are evaluating the switch:
sudo apt install -y openjdk-25-jdk
After install, both live side by side under /usr/lib/jvm/. The package does not change which java is the default. That is handled by the alternatives system in Step 6.
Confirm the 25 binary runs:
/usr/lib/jvm/java-25-openjdk-amd64/bin/java --version
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
If the output is empty and you see no java yet, you have a clean slate. If something is already installed (common on images that ship Jenkins, Elasticsearch, or Tomcat), decide whether to keep it or purge it with apt purge 'openjdk-*' before continuing.
Step 2: Install OpenJDK 21 LTS from the Debian repo
OpenJDK 21 is the current long-term support release and the one most readers want. It ships in Debian 13 main with no extra repo:
sudo apt update
sudo apt install -y default-jdk openjdk-21-jdk
The default-jdk meta-package pulls in whatever the Debian release considers the default (OpenJDK 21 on trixie, OpenJDK 17 on bookworm). Adding openjdk-21-jdk explicitly pins the version so you do not get surprised when the meta-package’s preferred version shifts in a future release.
Verify:
java --version
javac --version
readlink -f $(which java)
Real output from the test VM:
openjdk 21.0.10 2026-01-20
OpenJDK Runtime Environment (build 21.0.10+7-Debian-1deb13u1)
OpenJDK 64-Bit Server VM (build 21.0.10+7-Debian-1deb13u1, mixed mode, sharing)
javac 21.0.10
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
The binary lives at /usr/lib/jvm/java-21-openjdk-amd64/. That path matters in a moment when you set JAVA_HOME.
Debian 12 difference: On bookworm, default-jdk resolves to openjdk-17-jdk. OpenJDK 21 is available in bookworm-backports:
# Debian 12 only, enable backports first
echo "deb http://deb.debian.org/debian bookworm-backports main" \
| sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update
sudo apt install -y -t bookworm-backports openjdk-21-jdk
Step 3: Install OpenJDK 25 for the latest LTS
OpenJDK 25 is the newer LTS line and Debian 13 security-backports it alongside 21. Useful if your workload targets the newest LTS or you are evaluating the switch:
sudo apt install -y openjdk-25-jdk
After install, both live side by side under /usr/lib/jvm/. The package does not change which java is the default. That is handled by the alternatives system in Step 6.
Confirm the 25 binary runs:
/usr/lib/jvm/java-25-openjdk-amd64/bin/java --version
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Next, check whether any JDK is already on the system. A fresh Debian netinst has none:
apt list --installed 2>/dev/null | grep -iE 'jdk|jre|java'
which java || echo "no java yet"
If the output is empty and you see no java yet, you have a clean slate. If something is already installed (common on images that ship Jenkins, Elasticsearch, or Tomcat), decide whether to keep it or purge it with apt purge 'openjdk-*' before continuing.
Step 2: Install OpenJDK 21 LTS from the Debian repo
OpenJDK 21 is the current long-term support release and the one most readers want. It ships in Debian 13 main with no extra repo:
sudo apt update
sudo apt install -y default-jdk openjdk-21-jdk
The default-jdk meta-package pulls in whatever the Debian release considers the default (OpenJDK 21 on trixie, OpenJDK 17 on bookworm). Adding openjdk-21-jdk explicitly pins the version so you do not get surprised when the meta-package’s preferred version shifts in a future release.
Verify:
java --version
javac --version
readlink -f $(which java)
Real output from the test VM:
openjdk 21.0.10 2026-01-20
OpenJDK Runtime Environment (build 21.0.10+7-Debian-1deb13u1)
OpenJDK 64-Bit Server VM (build 21.0.10+7-Debian-1deb13u1, mixed mode, sharing)
javac 21.0.10
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
The binary lives at /usr/lib/jvm/java-21-openjdk-amd64/. That path matters in a moment when you set JAVA_HOME.
Debian 12 difference: On bookworm, default-jdk resolves to openjdk-17-jdk. OpenJDK 21 is available in bookworm-backports:
# Debian 12 only, enable backports first
echo "deb http://deb.debian.org/debian bookworm-backports main" \
| sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update
sudo apt install -y -t bookworm-backports openjdk-21-jdk
Step 3: Install OpenJDK 25 for the latest LTS
OpenJDK 25 is the newer LTS line and Debian 13 security-backports it alongside 21. Useful if your workload targets the newest LTS or you are evaluating the switch:
sudo apt install -y openjdk-25-jdk
After install, both live side by side under /usr/lib/jvm/. The package does not change which java is the default. That is handled by the alternatives system in Step 6.
Confirm the 25 binary runs:
/usr/lib/jvm/java-25-openjdk-amd64/bin/java --version
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
On the test box this reports Debian 13 trixie:
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
NAME="Debian GNU/Linux"
VERSION_ID="13"
6.12.57+deb13-amd64
Next, check whether any JDK is already on the system. A fresh Debian netinst has none:
apt list --installed 2>/dev/null | grep -iE 'jdk|jre|java'
which java || echo "no java yet"
If the output is empty and you see no java yet, you have a clean slate. If something is already installed (common on images that ship Jenkins, Elasticsearch, or Tomcat), decide whether to keep it or purge it with apt purge 'openjdk-*' before continuing.
Step 2: Install OpenJDK 21 LTS from the Debian repo
OpenJDK 21 is the current long-term support release and the one most readers want. It ships in Debian 13 main with no extra repo:
sudo apt update
sudo apt install -y default-jdk openjdk-21-jdk
The default-jdk meta-package pulls in whatever the Debian release considers the default (OpenJDK 21 on trixie, OpenJDK 17 on bookworm). Adding openjdk-21-jdk explicitly pins the version so you do not get surprised when the meta-package’s preferred version shifts in a future release.
Verify:
java --version
javac --version
readlink -f $(which java)
Real output from the test VM:
openjdk 21.0.10 2026-01-20
OpenJDK Runtime Environment (build 21.0.10+7-Debian-1deb13u1)
OpenJDK 64-Bit Server VM (build 21.0.10+7-Debian-1deb13u1, mixed mode, sharing)
javac 21.0.10
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
The binary lives at /usr/lib/jvm/java-21-openjdk-amd64/. That path matters in a moment when you set JAVA_HOME.
Debian 12 difference: On bookworm, default-jdk resolves to openjdk-17-jdk. OpenJDK 21 is available in bookworm-backports:
# Debian 12 only, enable backports first
echo "deb http://deb.debian.org/debian bookworm-backports main" \
| sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update
sudo apt install -y -t bookworm-backports openjdk-21-jdk
Step 3: Install OpenJDK 25 for the latest LTS
OpenJDK 25 is the newer LTS line and Debian 13 security-backports it alongside 21. Useful if your workload targets the newest LTS or you are evaluating the switch:
sudo apt install -y openjdk-25-jdk
After install, both live side by side under /usr/lib/jvm/. The package does not change which java is the default. That is handled by the alternatives system in Step 6.
Confirm the 25 binary runs:
/usr/lib/jvm/java-25-openjdk-amd64/bin/java --version
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Confirm the Debian release first so you know which package pool you are pulling from:
cat /etc/os-release | head -3
uname -r
On the test box this reports Debian 13 trixie:
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
NAME="Debian GNU/Linux"
VERSION_ID="13"
6.12.57+deb13-amd64
Next, check whether any JDK is already on the system. A fresh Debian netinst has none:
apt list --installed 2>/dev/null | grep -iE 'jdk|jre|java'
which java || echo "no java yet"
If the output is empty and you see no java yet, you have a clean slate. If something is already installed (common on images that ship Jenkins, Elasticsearch, or Tomcat), decide whether to keep it or purge it with apt purge 'openjdk-*' before continuing.
Step 2: Install OpenJDK 21 LTS from the Debian repo
OpenJDK 21 is the current long-term support release and the one most readers want. It ships in Debian 13 main with no extra repo:
sudo apt update
sudo apt install -y default-jdk openjdk-21-jdk
The default-jdk meta-package pulls in whatever the Debian release considers the default (OpenJDK 21 on trixie, OpenJDK 17 on bookworm). Adding openjdk-21-jdk explicitly pins the version so you do not get surprised when the meta-package’s preferred version shifts in a future release.
Verify:
java --version
javac --version
readlink -f $(which java)
Real output from the test VM:
openjdk 21.0.10 2026-01-20
OpenJDK Runtime Environment (build 21.0.10+7-Debian-1deb13u1)
OpenJDK 64-Bit Server VM (build 21.0.10+7-Debian-1deb13u1, mixed mode, sharing)
javac 21.0.10
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
The binary lives at /usr/lib/jvm/java-21-openjdk-amd64/. That path matters in a moment when you set JAVA_HOME.
Debian 12 difference: On bookworm, default-jdk resolves to openjdk-17-jdk. OpenJDK 21 is available in bookworm-backports:
# Debian 12 only, enable backports first
echo "deb http://deb.debian.org/debian bookworm-backports main" \
| sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update
sudo apt install -y -t bookworm-backports openjdk-21-jdk
Step 3: Install OpenJDK 25 for the latest LTS
OpenJDK 25 is the newer LTS line and Debian 13 security-backports it alongside 21. Useful if your workload targets the newest LTS or you are evaluating the switch:
sudo apt install -y openjdk-25-jdk
After install, both live side by side under /usr/lib/jvm/. The package does not change which java is the default. That is handled by the alternatives system in Step 6.
Confirm the 25 binary runs:
/usr/lib/jvm/java-25-openjdk-amd64/bin/java --version
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
You need Java on your Debian server because something you want to run, a Tomcat app, a Jenkins agent, a Minecraft server, an Elasticsearch node, refuses to start without it. This guide gets you from zero to a working java --version on Debian 13 (trixie) and Debian 12 (bookworm), with OpenJDK 21 LTS as the default, plus clear paths for OpenJDK 17, OpenJDK 11, OpenJDK 25, and Oracle JDK when you need them.
Every command here was tested on a real Debian 13 box. The output blocks are copied from the terminal, not made up. You will see how to install JDK packages from Debian’s own repo, add Eclipse Temurin for versions Debian does not ship, switch the system default with update-alternatives, set JAVA_HOME cleanly, and compile a HelloWorld as a final sanity check.
Tested April 2026 on Debian 13 (trixie, kernel 6.12) with OpenJDK 21.0.10 LTS, OpenJDK 25.0.2, Eclipse Temurin 17.0.18 and 11.0.30, Oracle JDK 21.0.10 LTS. Debian 12 (bookworm) notes where packaging differs.
Prerequisites
- Debian 13 (trixie) or Debian 12 (bookworm), fresh or existing install
- A user with sudo, or root (examples use root for brevity)
- Outbound HTTPS for
deb.debian.org,packages.adoptium.net, anddownload.oracle.com - ~500 MB free disk for a single JDK, ~2 GB if you install all four covered here
Step 1: Check what you already have
Confirm the Debian release first so you know which package pool you are pulling from:
cat /etc/os-release | head -3
uname -r
On the test box this reports Debian 13 trixie:
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
NAME="Debian GNU/Linux"
VERSION_ID="13"
6.12.57+deb13-amd64
Next, check whether any JDK is already on the system. A fresh Debian netinst has none:
apt list --installed 2>/dev/null | grep -iE 'jdk|jre|java'
which java || echo "no java yet"
If the output is empty and you see no java yet, you have a clean slate. If something is already installed (common on images that ship Jenkins, Elasticsearch, or Tomcat), decide whether to keep it or purge it with apt purge 'openjdk-*' before continuing.
Step 2: Install OpenJDK 21 LTS from the Debian repo
OpenJDK 21 is the current long-term support release and the one most readers want. It ships in Debian 13 main with no extra repo:
sudo apt update
sudo apt install -y default-jdk openjdk-21-jdk
The default-jdk meta-package pulls in whatever the Debian release considers the default (OpenJDK 21 on trixie, OpenJDK 17 on bookworm). Adding openjdk-21-jdk explicitly pins the version so you do not get surprised when the meta-package’s preferred version shifts in a future release.
Verify:
java --version
javac --version
readlink -f $(which java)
Real output from the test VM:
openjdk 21.0.10 2026-01-20
OpenJDK Runtime Environment (build 21.0.10+7-Debian-1deb13u1)
OpenJDK 64-Bit Server VM (build 21.0.10+7-Debian-1deb13u1, mixed mode, sharing)
javac 21.0.10
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
The binary lives at /usr/lib/jvm/java-21-openjdk-amd64/. That path matters in a moment when you set JAVA_HOME.
Debian 12 difference: On bookworm, default-jdk resolves to openjdk-17-jdk. OpenJDK 21 is available in bookworm-backports:
# Debian 12 only, enable backports first
echo "deb http://deb.debian.org/debian bookworm-backports main" \
| sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update
sudo apt install -y -t bookworm-backports openjdk-21-jdk
Step 3: Install OpenJDK 25 for the latest LTS
OpenJDK 25 is the newer LTS line and Debian 13 security-backports it alongside 21. Useful if your workload targets the newest LTS or you are evaluating the switch:
sudo apt install -y openjdk-25-jdk
After install, both live side by side under /usr/lib/jvm/. The package does not change which java is the default. That is handled by the alternatives system in Step 6.
Confirm the 25 binary runs:
/usr/lib/jvm/java-25-openjdk-amd64/bin/java --version
The output shows the 25.0.2 build with trixie’s security patch suffix:
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Debian-1deb13u2)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Debian-1deb13u2, mixed mode, sharing)
Step 4: Install OpenJDK 17 or 11 from Eclipse Adoptium (Temurin)
Debian 13’s main repo carries only OpenJDK 21 and 25. Anything older (OpenJDK 17, 11, or 8) has to come from a third-party repo. The cleanest option is Eclipse Adoptium, which publishes Temurin builds with TCK-certified quality and a proper trixie suite.
Add the repo:
sudo apt install -y wget gpg apt-transport-https
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.adoptium.net/artifactory/api/gpg/key/public \
| sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update
The $(lsb_release -cs) expansion resolves to trixie on Debian 13 or bookworm on Debian 12, so the same block works for either. Install lsb-release first if it is missing: apt install -y lsb-release.
Now install the Temurin versions you need:
sudo apt install -y temurin-17-jdk temurin-11-jdk
Temurin 8, 22, 23, 24, 25, and 26 are also in the repo under the same temurin-N-jdk naming. List them with apt-cache search temurin | sort if you are curious.
Verify both binaries:
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --version
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java --version
The Temurin 17 output is first, and the build string names the upstream Adoptium project so you can tell it apart from Debian’s OpenJDK packaging:
openjdk 17.0.18 2026-01-20
OpenJDK Runtime Environment Temurin-17.0.18+8 (build 17.0.18+8)
OpenJDK 64-Bit Server VM Temurin-17.0.18+8 (build 17.0.18+8, mixed mode, sharing)
Step 5: Install Oracle JDK 21 (opt-in, LTS)
Use Oracle JDK only if your employer’s license already covers it or if you specifically need Oracle’s commercial features (Flight Recorder extensions, Mission Control, GraalVM Enterprise). For everything else, OpenJDK or Temurin is the same code without the licensing complexity.
Oracle publishes a .deb package. Download and install:
cd /tmp
wget -q https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
sudo dpkg -i jdk-21_linux-x64_bin.deb
The file is around 160 MB. The latest path always serves the current point release, so the same URL works past 21.0.10. Installation completes quickly and registers the binaries under /usr/lib/jvm/jdk-21.0.10-oracle-x64/.
Verify:
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java --version
Oracle’s build string makes the commercial provenance obvious:
java 21.0.10 2026-01-20 LTS
Java(TM) SE Runtime Environment (build 21.0.10+8-LTS-217)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.10+8-LTS-217, mixed mode, sharing)
Notice Oracle’s build string says Java(TM) SE and includes the LTS marker, which is how you tell it apart from OpenJDK at a glance. The Oracle postinst also registers the java alternative with a priority of 352403456, which forces it to win auto-mode selection. Step 6 shows how to override that if you do not want Oracle as the default.
Step 6: Switch between installed JDKs
After following the steps above, five JDKs are registered with update-alternatives. List them:
sudo update-alternatives --list java
All five paths show up:
/usr/lib/jvm/java-21-openjdk-amd64/bin/java
/usr/lib/jvm/java-25-openjdk-amd64/bin/java
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java
/usr/lib/jvm/temurin-11-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
Now switch the default with the interactive menu. This updates /usr/bin/java symlink for every user on the box:
sudo update-alternatives --config java
The menu shows every alternative with its priority. Pick the number next to the JDK you want:
There are 5 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
0 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 auto mode
* 1 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 2111 manual mode
2 /usr/lib/jvm/java-25-openjdk-amd64/bin/java 2511 manual mode
3 /usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/java 352403456 manual mode
4 /usr/lib/jvm/temurin-11-jdk-amd64/bin/java 1111 manual mode
5 /usr/lib/jvm/temurin-17-jdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
Do the same for the compiler, because java and javac are separate alternatives:
sudo update-alternatives --config javac
Running both is critical. If you switch java to 17 but forget javac, your runtime and compiler versions drift, and builds will fail with cryptic class-file errors.
For an older piece on this exact topic, see how to set the default Java version on Ubuntu and Debian. The principles carry over.
Step 7: Set JAVA_HOME system-wide
Most Java-based apps read JAVA_HOME instead of java from PATH. Jenkins, Tomcat, Maven, Gradle, and every IDE do. Set it in /etc/profile.d/ so every login shell picks it up:
sudo nano /etc/profile.d/java.sh
Paste this, adjusting the path if you picked a different JDK:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
Make it executable and load it in the current session:
sudo chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh
Confirm:
echo $JAVA_HOME
echo "java binary: $(which java)"
Both lines should report the 21 path:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
java binary: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
For a portable alternative that follows whatever the alternatives symlink points to, use /usr/lib/jvm/default-java:
export JAVA_HOME=/usr/lib/jvm/default-java
That way, switching with update-alternatives also shifts JAVA_HOME without editing the profile file. Handy on multi-tenant boxes.
Step 8: Smoke test with Hello.java
A HelloWorld proves the compiler and runtime agree on the same JDK:
mkdir -p /root/hello && cd /root/hello
nano Hello.java
Drop in this source. It prints the JDK’s own version string so you see which runtime actually ran the class:
public class Hello {
public static void main(String[] args) {
String version = Runtime.version().toString();
System.out.println("Hello from Debian 13 on JDK " + version);
}
}
Compile, then run:
javac Hello.java
java Hello
On the test VM with OpenJDK 21 active, this prints:
Hello from Debian 13 on JDK 21.0.10+7-Debian-1deb13u1
Here is the full terminal session as one screenshot:

If your output prints a different version than you expect, re-check update-alternatives --config java and update-alternatives --config javac. The runtime matches whichever /usr/bin/java is currently linked to.
Troubleshooting
Error: “E: Unable to locate package openjdk-17-jdk” on Debian 13
Debian 13’s main repo does not ship openjdk-17-jdk or openjdk-11-jdk. The only OpenJDK versions in trixie main are 21 and 25. For 17 or 11 on trixie, use the Adoptium Temurin repo from Step 4. Do not try to pull openjdk-17-jdk from the bookworm pool, glibc symbols differ and the package will not run reliably.
Error: “The java_home file does not exist”
This shows up when an app reads JAVA_HOME from a service unit that captured the old value before you edited /etc/profile.d/java.sh. systemctl daemon-reload does not re-read profile files. Fix by either setting Environment=JAVA_HOME=/usr/lib/jvm/... inside the service file, or using EnvironmentFile=/etc/profile.d/java.sh with the export keywords removed.
Error: “Oracle JDK installed but update-alternatives –display javac does not list it”
Oracle’s .deb registers most binaries with update-alternatives but skips javac as a master alternative in some versions. Register it manually:
sudo update-alternatives --install /usr/bin/javac javac \
/usr/lib/jvm/jdk-21.0.10-oracle-x64/bin/javac 3000
Then re-run sudo update-alternatives --config javac and pick the Oracle entry.
Error: “java: command not found” after install
Either the install step failed (check apt install output for 404s on packages.adoptium.net) or your shell opened before the alternatives symlink was in place. Fix by opening a new shell or running hash -r. If the problem persists, confirm /usr/bin/java exists with ls -la /usr/bin/java.
Secure the server you just set up
What to install next
Now that Java is working, the next pieces depend on what you are building. If you are running a JVM app on Debian, you probably also need a reverse proxy, a database, and a CI pipeline. Jenkins still rules the JVM CI world, so install Jenkins on Debian is a common next step. For the database, PostgreSQL on Debian is the usual choice for Spring Boot and Micronaut apps. For TLS termination or HTTP routing, Nginx on Debian handles it cleanly.
For build tooling, Maven is in the Debian repo as maven and pulls in a sane default JDK. For Gradle, use Gradle’s own distribution because Debian’s is always a few versions behind. Apache Ant is still alive for legacy projects.
Stuck deploying a JVM app in production, or need help wiring Java services into CI? We do Claude Code, Java, and DevOps consulting. Reach out at [email protected] for one-hour paid sessions to multi-week engagements.
Note: The first method installs early access build which is not the one you would generally use.
This doesn’t work for me. The files are there but java -version says it’s v16, and update-alternatives –config java doesn’t list any options for jdk 17
Debian 10
I guess you have multiple versions of Java. Set 17 as default. Alternatively uninstall any other version before you install Java 17.
After using the first and the seccond method, Im still having :
X@X:/X/X# sudo tee /etc/profile.d/jdk.sh < export JAVA_HOME=/opt/jdk17
> export PATH=\$PATH:\$JAVA_HOME/bin
> EOF
export JAVA_HOME=/opt/jdk17
export PATH=$PATH:$JAVA_HOME/bin
X@X:/X/X# source /etc/pro
profile profile.d/ protocols
X@X:/X/X# source /etc/pro
profile profile.d/ protocols
X@X:/X/X# source /etc/profile.d/jdk.sh
X@X:/X/X# echo $JAVA_HOME
/opt/jdk17
X@X:/X/X# java -version
openjdk version “1.8.0_292”
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.292-b10, mixed mode)
nice, It has run perfectly