Introduction

This guide is trying to show you the correct way of handling multiple Java runtimes on macOS.

What is /usr/libexec/java_home ?

As a Java developer, you know $JAVA_HOME, and most tools pick up this environment variable and work as expected. Under macOS, the situation is different.

By default macOS will invoke /usr/libexec/java_home when you type java, javac etc…​

% md5 /usr/libexec/java_home
MD5 (/usr/libexec/java_home) = d50afbfa4f5d05e8db7ad88c30390888
% md5 /usr/bin/java
MD5 (/usr/bin/java) = d50afbfa4f5d05e8db7ad88c30390888
% md5 /usr/bin/javac
MD5 (/usr/bin/javac) = d50afbfa4f5d05e8db7ad88c30390888

As you can see, all the java executables in /usr/bin are the same file as /usr/libexec/java_home. The directory /usr/bin is contained in $PATH by default, so any call to java or javac will execute /usr/libexec/java_home.

So it would be best if you played nice with java_home. Let’s do that. But first, install a compatible JDK.

Installing a JDK

Azul and BellSoft offer my two favourite OpenJDK distributions.

If you require FX support in your JDK, you want to choose the BellSoft Full JDK.

Both Azul and BellSoft offer aarch64 builds for macOS.

There are other OpenJDK builds available:

However, they don’t provide aarch64 builds for macOS. Not even Oracle does. You only get x64 builds for macOS, which are much slower than a native JDK/JRE. You don’t want that.

What’s mind-boggling is that Microsoft worked with Azul to bring a native JDK on the new Apple M1. They had builds available in December 2020.

Zulu JDK

Navigate to https://www.azul.com/downloads/ and download your desired JDK:

azul download

If you are using an M1 Mac, you want to choose ARM 64-bit . Otherwise choose x86 or x86 64-bit.

Select the .dmg download option. Do not choose the .zip option.

Install the Zulu JDK:

Just double-click the downloaded .dmg file. A window will open:

azul installation 01

Follow the instructions and double-click the icon. After the setup you will have a new directory inside /Library/Java/JavaVirtualMachines. In my case it created the new directory zulu-16.jdk.

If you execute java_home now, you will get the current $JAVA_HOME path:

% /usr/libexec/java_home
/Library/Java/JavaVirtualMachines/zulu-16.jdk/Contents/Home

Liberica JDK

Liberica is the name of BellSofts JRE/JDK.

Navigate to https://bell-sw.com/pages/downloads/ and download your desired JDK:

liberica download

If you are using an M1 Mac, you want to choose ARM. Otherwise choose x86. Choose "Full JDK" if you require FX support. Otherwise Standard JDK is sufficient.

Select the .dmg or .pkg download option. Do not choose the .zip option.

Install the Bellsoft Liberica JDK:

Just double-click the downloaded .dmg or .pkg file. A window will open:

liberica installation 01

Follow the instructions and double-click the icon. After the setup you will have a new directory inside /Library/Java/JavaVirtualMachines. In my case it created the new directory liberica-jdk-8-full.jdk.

If you execute java_home -v 1.8 now, you will get the correct $JAVA_HOME:

% /usr/libexec/java_home -v 1.8
/Library/Java/JavaVirtualMachines/liberica-jdk-8-full.jdk/Contents/Home

Notice that I used -v 1.8 this time. Try /usr/libexec/java_home -v 16 and you will understand.

Edit your .zshrc

java_home will pick up $JAVA_HOME and it will handle the rest.

My idea is to define the default JDK in my ~/.zshrc:

export JAVA_HOME=$(/usr/libexec/java_home -v 16)

You can omit this change if you want the latest JDK your default one. java_home seems to sort the installed JDKs from high to low.

The manpage isn’t too chatty about this:

By default, if no constraints match the available list of JVMs, the default order is used.

I also added a function to my ~/.zshrc:

sj () {
  echo "switching to java $1"
  export JAVA_HOME=$(/usr/libexec/java_home -v $1)
  echo "new JAVA_HOME is now $JAVA_HOME"
}

sj as in "switch java". Choose a different name if you like.

After you modified your ~/.zshrc, reload the changes:

source ~/.zshrc

Switch your java runtime

% sj 1.8
switching to java 1.8
new JAVA_HOME is now ...
% java -version
openjdk version "1.8.0_292"
OpenJDK Runtime Environment (build 1.8.0_292-b11)
OpenJDK 64-Bit Server VM (build 25.292-b11, mixed mode)
% sj 16
switching to java 16
new JAVA_HOME is now ...
% java -version
openjdk version "16.0.1" 2021-04-20
OpenJDK Runtime Environment Zulu16.30+19-CA (build 16.0.1+9)
OpenJDK 64-Bit Server VM Zulu16.30+19-CA (build 16.0.1+9, mixed mode)

Switching your Java runtime is more manageable now.