- Published on
MacOS JDK path
An overlook of the JDK path on MacOS.
There're several symlinks targeted to system java lib directory from brew-installed JDK. And JEnv could help to mange and switch multiple JDK versions.
MacOS Library folder levels
On MacOS system, there's 3 levels of Library folder in simply terms:
- The user library,
~/Library, stores per-user settings etc.
- The local library,
/Library, stores computer-wide settings etc.
- The system library,
/System/Library, stores the base settings, resources, etc that comes with the system. (In theory, you shouldn't change anything in here.)
Normally, the JDK path lays on
/Library/Java/JavaVirtualMachines/. However, if JDK installed via IntelliJ, it will be
Things become little complicated when comes to Homebrew. First, let's install the latest version of JDK via brew.
$ brew install openjdk # or (the java is an alias for openjdk) $ brew install java
By the way, the JDK migrated from homebrew/cask to homebrew/core, and brew 4 replaced the git repo with JSON file for metadata, you don't have to tap any taprooms unless you want to install a non-LTS version(in such case, you need
homebrew/cask-versions) or from other third-party vendors.
After successfully installed, brew will prompts something like this:
==> Caveats For the system Java wrappers to find this JDK, symlink it with sudo ln -sfn /usr/local/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk openjdk is keg-only, which means it was not symlinked into /usr/local, because macOS provides similar software and installing this software in parallel can cause all kinds of trouble. If you need to have openjdk first in your PATH, run: echo 'export PATH="/usr/local/opt/openjdk/bin:$PATH"' >> ~/.zshrc For compilers to find openjdk you may need to set: export CPPFLAGS="-I/usr/local/opt/openjdk/include" ==> Summary 🍺 /usr/local/Cellar/openjdk/20.0.1: 636 files, 322.4MB
As suggested, we need to run
sudo ln -sfn /usr/local/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk to symlink our installed JDK to system Java wrapper. How this works? That command just creates a soft link from JDK installation to system java lib folder. But when we run
which java, it shows like this:
$ which java /usr/bin/java
/usr/bin/java? Where does the
JavaVirtualMachines folder go?
/usr/bin/java is system wrapper tool that locates and executes the actual
java executables, which means it will look for available java versions under
JavaVirtualMachines folder. And MacOS system provides a tool to locate the effective
Ok, that explained something, but how about the line shown the real JDK installation path? Why
/usr/local/Cellar/openjdk instead of
Well, it's symlink again. Roughly as the purpose of
JavaVirtualMachines folder for multiple versions, Homebrew installs formulae to the Cellar at
$(brew --cellar) and then symlinks some of the installation into the prefix at
$(brew --prefix). We can see the linking like this:
$ ls -l /Library/Java/JavaVirtualMachines/openjdk.jdk lrwxr-xr-x 1 root wheel 42B Aug 15 2020 /Library/Java/JavaVirtualMachines/openjdk.jdk -> /usr/local/opt/openjdk/libexec/openjdk.jdk $ ls -l /usr/local/opt/openjdk lrwxr-xr-x 1 kid admin 24B May 20 15:46 /usr/local/opt/openjdk -> ../Cellar/openjdk/20.0.1 $ ls -l /usr/local/Cellar/openjdk/20.0.1 total 56 -rw-r--r-- 1 kid admin 3.5K May 20 15:46 INSTALL_RECEIPT.json -rw-r--r-- 1 kid admin 19K Mar 7 21:51 LICENSE -rw-r--r-- 1 kid admin 424B Mar 7 21:51 README.md drwxr-xr-x 31 kid admin 992B Mar 7 21:51 bin drwxr-xr-x 10 kid admin 320B Mar 7 21:51 include drwxr-xr-x 3 kid admin 96B Mar 7 21:51 libexec drwxr-xr-x 3 kid admin 96B Mar 7 21:51 share
The folders are different for Apple Silicon machines, it would be
/opt/homebrew/Cellar instead of
/opt/homebrew/opt instead of
JEnv is a CLI tool to facilitate managing and switching multiple JDK versions on MacOS/Linux.
Quickly get started:
$ brew install jenv $ echo 'export PATH="$HOME/.jenv/bin:$PATH"' >> ~/.bash_profile $ echo 'eval "$(jenv init -)"' >> ~/.bash_profile $ jenv enable-plugin export # to export JAVA_HOME # add JDK to Jenv $ jenv add /Library/Java/JavaVirtualMachines/openjdk.jdk/ # list all managed jdk versions $ jenv versions * system (set by /Users/kid/.jenv/version) 20.0 20.0.1 openjdk64-20.0.1 # config a global version $ jenv global 20.0 # config a local(per-directory) version $ jenv local 20.0 # config a (current) shell version $ jenv shell 20.0
We can now the JEnv script is taking over the control of responsibility to locate Java executables. Yes, just another wrapper, another abstract layer.
$ which java /Users/kid/.jenv/shims/java
JEnv provides more dynamical and fine-grained control of locating
java. Essentially, it looks different registered Java versions under
~/.jenv/versions by given condition. For each specifying version command, JEnv will export the corresponding JAVA_HOME env var. Hence each time firing
java, the JEnv shim will redirect to the specific java executable (by default, is the system Java wrapper).
$ jenv which java /usr/bin/java $ jenv shell 20.0 $ jenv which java /Users/kid/.jenv/versions/20.0/bin/java