Java 25 is the current long-term support release, out since September 2025, and it’s the version to start on if nothing is forcing your hand. Java 21 is still everywhere and will be for years, but 25 is where the permissive licence terms and the newest tooling live now.
Installing it is mostly straightforward. Mostly. There are three places people get stuck, and none of them are hard once you know: your distribution may not package it, Debian 12 definitely doesn’t, and 32-bit x86 support was removed outright in this release.
This guide covers installing Java 25 across the major distributions, choosing a build, setting JAVA_HOME so it survives a reboot, and switching between versions when you have several installed. If the shell side is new to you, Linux commands and directory structure covers the ground it assumes.
Check your architecture first
Start here, because it’s the one failure that looks like something else entirely.
JDK 25 removed the 32-bit x86 port. That was JEP 503, and it landed in this release. If you’re on 32-bit x86 hardware or a 32-bit userland, there is no Java 25 for you. Not a packaging gap, not something a different repository fixes. The port is gone.
uname -m
x86_64 or aarch64 and you’re fine. If that prints i686 or i386, stay on Java 21, which still supports it.
Most people will never hit this. It matters for older embedded boxes and the occasional ancient VM nobody has rebuilt.
JDK or JRE, and which build
Two decisions before you type anything.
The first is easy. Install the JDK, not the JRE. The JDK includes the compiler and the diagnostic tooling; the JRE only runs things. Separate JRE packages have mostly disappeared anyway, and even on a server that just runs a JAR you’ll want jcmd and jstack the first time something misbehaves at 2am.
The second is which build. “Java 25” is a specification, and several organisations ship it:
| Build | Licence | Worth knowing |
|---|---|---|
| Your distro’s OpenJDK package | GPLv2 with Classpath Exception | Simplest. Patched alongside everything else on the system. |
| Eclipse Temurin | GPLv2 with Classpath Exception | The usual answer when your distro doesn’t package it. SDKMAN installs these. |
| Amazon Corretto | GPLv2 with Classpath Exception | Long-term support from AWS. Common on EC2. |
| Azul Zulu | GPLv2 with Classpath Exception | Free builds, commercial support available. |
| Oracle JDK | Oracle NFTC | Free-use terms currently apply to 25. They did not survive on 21. |
That last row is worth understanding rather than skipping. Oracle ships its JDK under No-Fee Terms and Conditions for a window, then moves updates to a paid licence. That already happened to JDK 21: updates released from September 2026 onward fall under the Oracle Technology Network licence, which is not free for most commercial use. Java 25 currently sits inside its free window, and one day it won’t.
None of that touches the OpenJDK builds. Temurin, Corretto, Zulu and your distribution’s own packages are GPL and stay GPL. Install Java from apt or dnf and Oracle’s licensing calendar is simply not your problem.
Unless someone has deliberately bought Oracle Java support, install an OpenJDK build. That used to be a preference. Watching what happened to 21 makes it the default.
Installing on Ubuntu
Java 25 is packaged for Ubuntu 22.04 and later:
sudo apt update
sudo apt install openjdk-25-jdk
One catch that differs from earlier releases. Java 21 sits in main on Ubuntu 24.04, but Java 25 is in universe on every Ubuntu release that has it, 22.04 and 24.04 included. Universe is enabled by default on ordinary installs, and frequently is not on minimal cloud images, containers and some server builds.
If apt claims it can’t find the package, that’s almost always why:
sudo add-apt-repository universe
sudo apt update
sudo apt install openjdk-25-jdk
On Ubuntu 20.04 there’s no Java 25 package. Either upgrade, since 20.04 left standard support in 2025, or use SDKMAN further down, which installs Java 25 under your own user and needs no root.
Installing on Debian
Debian and Ubuntu diverge here, and guides that treat them as interchangeable will send you in circles.
Debian 12 (bookworm) has no openjdk-25-jdk package. Not in main, not in backports. Bookworm shipped around Java 17 and that’s what it carries. Running the apt command on Debian 12 fails, and no amount of apt update changes it.
Debian 13 (trixie), the current stable, does package it:
sudo apt update
sudo apt install openjdk-25-jdk
Still on Debian 12? SDKMAN, further down, is the way in. It installs per user, so you get Java 25 without touching apt or waiting for a distro upgrade.
Installing on RHEL, Rocky, AlmaLinux and Fedora
sudo dnf install java-25-openjdk-devel
Note the -devel suffix. Drop it and you get java-25-openjdk, which is the runtime with no javac. People install the shorter name, find they can’t compile, and assume the install broke.
Availability on RHEL rebuilds depends on your minor version, since new OpenJDK streams arrive in AppStream on Red Hat’s schedule rather than the OpenJDK project’s. Check before you assume:
dnf search java-25-openjdk
Nothing returned means your release hasn’t picked it up yet. SDKMAN, further down, covers you without root.
Several major versions can coexist happily, which is what the alternatives section further down is for.
Verifying the install
java -version
javac -version
Both should report 25. If java answers and javac doesn’t, you have a runtime rather than a JDK, which on RHEL means the missing -devel.
To find where the binary actually lives, follow the symlinks instead of trusting which:
readlink -f $(which java)
On Debian-family systems that lands under /usr/lib/jvm/java-25-openjdk-amd64/bin/java. On RHEL it’s also under /usr/lib/jvm/, with a longer versioned directory name. That path is what the next section needs.
Setting JAVA_HOME so it sticks
Plenty of tooling reads JAVA_HOME rather than searching your path. Maven, Gradle, Tomcat and most application servers all care.
The classic mistake is exporting it in a terminal, confirming it works, then finding it gone tomorrow. That set it for one shell and nothing else.
For your own user:
echo 'export JAVA_HOME=$(readlink -f $(which java) | sed "s:/bin/java::")' >> ~/.bashrc
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
Deriving the path from which java rather than hardcoding it means it keeps working after a point release changes the directory name, which happens more often than you’d like. The same trick works for any tool you want on the path, and pairs well with shell aliases for the commands you type most.
System-wide, use a file in /etc/profile.d/ rather than editing /etc/profile, which package updates overwrite:
sudo tee /etc/profile.d/java.sh > /dev/null <<'EOF'
export JAVA_HOME=/usr/lib/jvm/java-25-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
EOF
sudo chmod +x /etc/profile.d/java.sh
Match that path to whatever readlink -f reported. Files in profile.d are sourced at login, so open a new session to pick it up.
Worth knowing: /etc/profile.d applies to login shells. A systemd service never sees it. If you run a Java application under systemd, set the variable in the unit with Environment=, or the service starts with whatever the system default happens to be. Understanding systemd service management covers unit files properly.
Running more than one Java version
Having 21 and 25 side by side is normal. Both families handle it through alternatives, under different command names.
Debian and Ubuntu:
sudo update-alternatives --config java
RHEL, Rocky, AlmaLinux, Fedora:
sudo alternatives --config java
Either prints a numbered list of installed JDKs with the current one marked, and asks you to pick. Choose a number and /usr/bin/java gets repointed.
The trap: java and javac are separate alternatives links. Switching one leaves the other alone, which produces the genuinely confusing situation where java -version reports 25 and javac -version reports 21. Switch both:
sudo update-alternatives --config javac
If you switch often, particularly on a development machine, SDKMAN hurts less. It installs per user, needs no root, and switches per shell:
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk list java
sdk install java 25-tem
sdk use java 25-tem
sdk use affects the current shell only; sdk default makes it permanent. Take the exact version identifier from sdk list java rather than copying one out of a blog post, this one included, because those strings change as builds are released.
Piping a script off the internet into bash deserves a second’s thought on a production box. On your laptop, fine. On a server, download it, read it, then run it.
Compiling and running something
Worth doing once to prove the toolchain works end to end. The traditional version still compiles exactly as it always did:
public class Hello {
public static void main(String[] args) {
System.out.println("Java 25 is working");
}
}
javac Hello.java
java Hello
No .class on the second command. You pass the class name, not the filename.
Java 25 finalised something that makes that boilerplate optional. JEP 512, compact source files and instance main methods, became a permanent feature in this release after several rounds as a preview. The whole program can now be:
void main() {
System.out.println("Java 25 is working");
}
No class declaration, no static, no String[] args. Save it as Hello.java and run it directly:
java Hello.java
That compiles in memory and runs, no .class file left behind. It has been possible to run single files this way since Java 11, but combined with JEP 512 the ceremony is finally gone. For teaching, quick tests and small scripts this is a genuine improvement. It is not a replacement for a real project layout.
Java 25 also ships jshell, the interactive REPL, which remains the fastest way to check whether an API does what you think:
jshell
Building a runnable JAR
Compile first, then package with an entry point so the JAR knows where to start:
javac Hello.java
jar cfe hello.jar Hello Hello.class
java -jar hello.jar
The flags read as c create, f output filename, e entry-point class. Order matters, because the values have to appear in the same order as their flags: hello.jar pairs with f, Hello with e.
Leave out e and the JAR builds fine, then fails at runtime with “no main manifest attribute”, which is the most common JAR error there is.
For anything real, use Maven or Gradle. Doing it by hand once is about understanding what those tools do for you.
Troubleshooting
| Symptom | Cause |
|---|---|
Unable to locate package openjdk-25-jdk on Ubuntu |
universe not enabled. Java 25 is in universe on every Ubuntu release. |
| Same error on Debian 12 | Expected. Bookworm doesn’t package it, backports included. Use SDKMAN. |
dnf search java-25-openjdk returns nothing |
Your RHEL minor version hasn’t picked up the stream. Use SDKMAN. |
javac: command not found but java works |
Runtime only. On RHEL you want the -devel package. |
| No Java 25 build for your machine at all | Check uname -m. 32-bit x86 was removed in JDK 25. |
java -version and javac -version disagree |
Separate alternatives links. Switch both. |
JAVA_HOME empty inside a systemd service |
/etc/profile.d is login shells only. Use Environment= in the unit. |
JAVA_HOME gone after logout |
Exported in one shell. Put it in ~/.bashrc or /etc/profile.d. |
no main manifest attribute |
JAR built without an entry point. Use jar cfe. |
UnsupportedClassVersionError |
Compiled on a newer JDK than the one running it. Check both. |
Should you install 25 or stay on 21?
Install Java 25 for anything new. It’s the current LTS, it’s inside its permissive licence window, and starting one release behind buys you nothing.
Stay on Java 21 when you’re matching an existing environment, when a framework or vendor hasn’t certified 25, or when you’re on 32-bit x86 and have no choice. Java 21 is supported by the OpenJDK vendors for years yet, so this is not urgent.
The licensing point holds either way. Use an OpenJDK build unless someone deliberately bought Oracle support, and the whole question of free-use windows stops applying to you.
Still running the previous LTS, or an older one? The same steps work with the version numbers swapped, and installing and managing Java 17 on Linux covers that specifically. The alternatives section above is what you want once more than one is on the same machine.


