Installing Java on Ubuntu (without breaking your shell)

TL;DR -- Different shells store environment variables in different locations. Set environment variables using your shell's recommended location.

There are plenty of resources out there for installing Java on Ubuntu, but I wanted to write one that covers the exact issue I ran into when installing Java with zsh.

This post focuses on the process for installing Java with zsh as compared to installing Java with bash. If you've no interest in zsh and just want to know how to bang it out with bash, I suggest reading this Digital Ocean post instead.

Update Package Manager

Before doing anything, it's a good idea to update our package manager:

sudo apt update
sudo apt upgrade

With that out of the way, the first thing I like to do is clear out any existing Java installations before making any other changes.

Check the existing Java installation

We can start by seeing what our existing Java setup looks like by issuing a command:

# WSL
java -version # press enter

# output
openjdk version "17.0.5" 2022-10-18
OpenJDK Runtime Environment (build 17.0.5+8-Ubuntu-2ubuntu122.04)
OpenJDK 64-Bit Server VM (build 17.0.5+8-Ubuntu-2ubuntu122.04, mixed mode, sharing)

Remove existing Java installation

You may or may not want to remove an existing Java installation. I prefer to start fresh, just in case.

In the previous step, I saw that I had the OpenJDK packages installed for Java, so I proceed with removing them.

Note: the comments add context (actions such as tab completions, saving files, re-opening the terminal)

# WSL
sudo apt remove --autoremove openjdk- # press tab repeteadly

# installed packages should be listed with tab completion

# after pressing tab, it autocompletes to:
sudo apt remove --autoremove openjdk-17-jre-headless:amd64 # press enter

# if more than one package appeared in the previous step, make sure to remove all of them

# source the shell (or close and re-open your terminal application)
source ~/.zshrc

# confirm Java is no longer installed
java -version # press enter

# output
zsh: command not found: java

Install desired Java version

Now we can install the appropriate version of Java (more specifically, the JDK). In my case, it's version 17.

So, we can just add 17 to openjdk- in the command below (replace 17 with whatever version you need to install):

sudo apt install openjdk-17 # press tab to see packages listed

# output
openjdk-17-dbg           openjdk-17-jdk           openjdk-17-jre-headless
openjdk-17-demo          openjdk-17-jdk-headless  openjdk-17-jre-zero
openjdk-17-doc           openjdk-17-jre           openjdk-17-source

# we want a headless JDK, we so end up with:
sudo apt install openjdk-17-jdk-headless

# source the shell or restart the terminal
source ~/.zshrc

# confirm Java installation
java -version # press enter

# output
openjdk version "17.0.7" 2023-04-18
OpenJDK Runtime Environment (build 17.0.7+7-Ubuntu-0ubuntu122.04.2)
OpenJDK 64-Bit Server VM (build 17.0.7+7-Ubuntu-0ubuntu122.04.2, mixed mode, sharing)

Set the $JAVA_HOME environment variable

We're not quite done yet, though. We need to set the $JAVA_HOME environment variable.

I'll first describe the way I initially tried it so that I can illustrate how much more straightforward the correct way is.

The wrong way

This is where I got thrown off by zsh. I didn't realize that zsh handles environment variables differently than bash.

I made the mistake of adding JAVA_HOME to my /etc/environment file (the bash way). The result went something like this:

# /etc/environment
JAVA_HOME="/path/to/java/home"

# save and exit /etc/environment

# source the file
source /etc/environment

echo $JAVA_HOME # outputs /path/to/java/home, yay!

# close terminal 
# open new terminal
echo $JAVA_HOME # no output, uhh.. wtf?

At this point, I thought I would get clever and source /etc/environment within my .zshrc

It almost worked:

# .zshrc
source /etc/environment

# save and exit .zshrc
# close and re-open terminal

echo $JAVA_HOME # success!

So far so good, but when I try to open a project with VS Code from the terminal...

code . # presss enter

zsh: command not found: code

GRRR!

I decide to get clever once again, and add my entire $PATH variable to my /etc/environment file:

# /etc/environment
PATH="/home/this/path:/home/that/path:/another/path:/vs/code/path"
JAVA_HOME="/java/home/path"

This solution actually worked, but it felt so hacky. I just knew there had to be a better way...

The right way

Before we jump in, note that the configuration has been reset from the previous section, (titledThe wrong way):

  • source /etc/environment removed from .zshrc

  • JAVA_HOME variable deleted from /etc/environment

  • PATH deleted from /etc/environment

  • the terminal was session closed and re-opened

Now we can go about setting JAVA_HOME properly:

echo $JAVA_HOME # press enter
# no output 

# find the Java install path
sudo update-alternatives --config java # press enter

# output
There is only one alternative in link group java (providing /usr/bin/java): 
/usr/lib/jvm/java-17-openjdk-amd64/bin/java
Nothing to configure.

From here we can copy the path that was output: /usr/lib/jvm/java-17-openjdk-amd64/bin/java

The place you put that path will depend on a few factors:

  • which shell you're using

  • whether this Java configuration is being set at the profile level or the system level

bashzsh
profile-level~/.bashrc~/.zshenv
system-level/etc/environment/etc/zsh/zshenv

Luckily, what we put in the file is the same, regardless of scope or shell:

# at the bottom of the appropriate file from the table above
JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64/bin/java"

Save and exit the file, then close and re-open the terminal.

Now we can see that JAVA_HOME is set if we echo it out:

echo $JAVA_HOME

# output
/usr/lib/jvm/java-17-openjdk-amd64/bin/java

That's it! Your Java configuration is ready to go, and your shell commands remain intact.