To set JAVA_HOME on Ubuntu, find your JDK's real install path, then export JAVA_HOME pointing at that directory (not the bin subfolder) in the right startup file for the scope you want — ~/.bashrc for one user, /etc/environment for the whole machine. The one mistake nearly everyone makes on the first try is pointing JAVA_HOME at the java executable or its bin directory instead of the JDK root. Maven, Gradle, and every build tool expect JAVA_HOME to be the top of the JDK so they can find $JAVA_HOME/bin/java and $JAVA_HOME/lib underneath it.
Step 1: Confirm a JDK is installed and find its path
First check you have a JDK, not just a runtime:
javac -version # a compiler means you have a JDK, not only a JRE
java -version
If javac is missing, install a JDK — OpenJDK from the Ubuntu repositories is the standard choice:
sudo apt update
sudo apt install openjdk-17-jdk
Now find the real path. The reliable way follows the symlink chain that which java starts:
readlink -f $(which java)
# e.g. /usr/lib/jvm/java-17-openjdk-amd64/bin/java
Strip the trailing /bin/java — the part you want for JAVA_HOME is the directory above bin:
/usr/lib/jvm/java-17-openjdk-amd64
On a typical Ubuntu install, JDKs live under /usr/lib/jvm/, so ls /usr/lib/jvm/ shows every version you have.
Step 2: Choose the scope
Where you set JAVA_HOME determines who gets it, and the wrong scope is a common cause of "it works in my shell but the service can't find Java."
~/.bashrc— one user, interactive shells. Fine for a developer laptop.~/.profile— one user, login shells including many desktop-launched apps. Better if GUI-launched IDEs need the variable./etc/environment— every user, system-wide. The right choice on a server or CI host where services run as their own accounts.
Pick one. Setting it in several places with different values is how machines end up disagreeing with themselves.
Step 3: Set it (per-user)
For a single user, append to ~/.bashrc:
echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' >> ~/.bashrc
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
The second line puts $JAVA_HOME/bin first on PATH, so java and javac resolve to the JDK you just named — worth doing so JAVA_HOME and the commands on your path never disagree. source reloads the file so the current shell picks it up without a logout.
Step 3 (alternative): Set it system-wide
For all users and services, edit /etc/environment. Note this file is not a shell script — it holds plain KEY=value lines with no export:
sudo nano /etc/environment
Add:
JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
Because it is not a script, you cannot reference $JAVA_HOME inside it to extend PATH the way you would in .bashrc; set PATH per-shell or in profile scripts instead. Changes take effect on next login (or source /etc/environment for the value, though PATH edits there need a re-login).
Step 4: Verify it stuck
echo $JAVA_HOME
# /usr/lib/jvm/java-17-openjdk-amd64
ls $JAVA_HOME/bin/java # should exist
mvn -version # tools should now report the right JAVA_HOME
If mvn -version reports a different JAVA_HOME, something else set it later in your startup chain — grep for it: grep -r JAVA_HOME ~/.bashrc ~/.profile /etc/environment /etc/profile.d/.
Managing multiple JDKs with update-alternatives
If you keep several JDKs installed, update-alternatives is Ubuntu's built-in switcher for which java/javac the system uses by default:
sudo update-alternatives --config java
It prints a numbered menu of installed JDKs and lets you pick the system default. One caveat that surprises people: update-alternatives changes which java runs, but it does not update JAVA_HOME — that variable is yours to maintain. After switching the default JDK, update your JAVA_HOME export to match, or your build tool and your command line will use different Java versions.
Why the JDK version is a security concern, not just config
Setting JAVA_HOME correctly is plumbing; keeping it pointed at a supported JDK is security. JDK releases carry fixes for vulnerabilities in the runtime and standard libraries, and only current release lines receive them. Pointing JAVA_HOME at an ancient JDK that happens to be installed means every build and every service launched from it runs on an unpatched runtime.
Two habits keep this honest. Prefer LTS releases (17, 21) for production, since they receive extended updates. And treat the JDK like any other dependency: an SCA tool inventorying your build and runtime environment flags an out-of-support JDK the same way it flags a stale library, which is easy to miss when the version is buried in an environment variable nobody has looked at since setup. If you hit a NullPointerException or other runtime error while getting a new JDK working, our guide on diagnosing java.lang.NullPointerException walks through reading the modern JVM messages.
FAQ
What exactly should JAVA_HOME point to on Ubuntu?
The JDK root directory — the one that contains bin/, lib/, and conf/, for example /usr/lib/jvm/java-17-openjdk-amd64. Do not point it at the bin folder or at the java executable itself; build tools append /bin/java to whatever you set.
Where is Java installed on Ubuntu?
Under /usr/lib/jvm/ for packages installed via apt. Run readlink -f $(which java) to trace the active binary back to its JDK, then strip the trailing /bin/java to get the JAVA_HOME value.
Should I set JAVA_HOME in .bashrc or /etc/environment?
~/.bashrc (or ~/.profile) for a single developer account; /etc/environment for a server or CI host where services run under their own users. Remember /etc/environment is not a shell script, so use plain KEY=value lines with no export.
Why does JAVA_HOME still point to the old version after switching JDKs?
update-alternatives --config java changes the default java binary but never touches JAVA_HOME. Update the JAVA_HOME export yourself to match the JDK you switched to, or the variable and the command-line java will disagree.