To install Java on Mac with brew, run brew install openjdk (or a pinned LTS like brew install openjdk@21), symlink it into /Library/Java/JavaVirtualMachines so macOS's java_home can see it, and set JAVA_HOME in your shell profile. That is the whole procedure — three commands — but each step has a reason, and skipping the symlink is why half the "I installed Java but java -version says command not found" threads exist. This guide covers the clean path, the choice between Homebrew's OpenJDK builds and Temurin, and the part most tutorials skip: keeping the JDK you installed patched.
Why brew, and not a downloaded .dmg
You can absolutely install java using brew alternatives — Oracle's installer, Adoptium's .pkg, or a vendor download. Homebrew wins for developers on three grounds:
- Updates are one command.
brew upgradepatches your JDK along with everything else. JDKs get quarterly security updates (CPU releases in January, April, July, October); an installer-based JDK is patched exactly as often as you remember it exists, which in practice is never. - Versions are managed, not scattered. Multiple JDKs coexist under Homebrew's prefix instead of accumulating in
/Library/Javafrom years of one-off installers. - Uninstalls are real.
brew uninstall openjdk@17actually removes it.
The security argument matters more than people credit. An unpatched JRE from 2023 sitting in your PATH is the java mac install equivalent of an unpatched browser — deserialization and TLS fixes ship in those quarterly updates.
Step 1: install the JDK
For the latest OpenJDK:
brew install openjdk
For a specific LTS release — the right choice for most project work, since frameworks and build tooling target LTS lines (17 and 21 are the current LTS versions as of this writing):
brew install openjdk@21
# or
brew install openjdk@17
Prefer a vendor-built distribution with its own support lifecycle? Eclipse Temurin (the Adoptium project's builds) installs as a cask:
brew install --cask temurin
# pinned major version:
brew install --cask temurin@21
Difference in behavior worth knowing: the openjdk formulae are keg-only — Homebrew does not link them into your PATH or register them with macOS, to avoid shadowing Apple's Java stubs. The Temurin cask, by contrast, installs a full .pkg into /Library/Java/JavaVirtualMachines, so it is visible to macOS immediately with no symlink step.
Step 2: the symlink (the step everyone misses)
macOS discovers JDKs through /usr/libexec/java_home, which scans /Library/Java/JavaVirtualMachines. Because Homebrew's openjdk is keg-only, you connect it manually — brew prints this exact command in its post-install caveats:
# Apple Silicon
sudo ln -sfn /opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk \
/Library/Java/JavaVirtualMachines/openjdk-21.jdk
# Intel Macs use /usr/local instead of /opt/homebrew
Verify macOS can now see it:
/usr/libexec/java_home -V
You should get a list of installed JDKs with versions, vendors, and paths. If your JDK is missing from this list, the symlink is wrong — that, or you installed openjdk but are checking before linking.
Step 3: JAVA_HOME and PATH
Build tools (Maven, Gradle) and IDE launchers key off JAVA_HOME. Set it from java_home rather than hard-coding a Homebrew cellar path that changes on upgrade:
# ~/.zshrc
export JAVA_HOME=$(/usr/libexec/java_home -v 21)
export PATH="$JAVA_HOME/bin:$PATH"
Then source ~/.zshrc and confirm:
java -version
# openjdk version "21.0.x" ...
echo $JAVA_HOME
Using java_home -v 21 means "give me whatever 21.x is currently installed," so quarterly patch upgrades never break your environment.
Multiple Java versions
Installing several JDKs side by side is normal — one legacy service on 17, new work on 21. Install each formula, symlink each, and switch by changing which version java_home resolves:
jdk() { export JAVA_HOME=$(/usr/libexec/java_home -v "$1"); }
# usage: jdk 17 / jdk 21
If you switch constantly or need per-project pinning, a version manager (jenv or SDKMAN) is the better tool — we cover that workflow in a companion post on changing Java versions on a Mac.
Keeping it patched (the part that matters after day one)
The install is not the finish line. Two habits:
brew update && brew upgrade
Run it routinely (or let a scheduled task do it). JDK CPU releases fix real vulnerabilities; the whole point of a package-managed brew java setup is that patching is free.
And know what your projects run, not just your laptop. Your machine having a patched JDK does nothing for the CI image still building on an EOL Java 8, or the app pulling a vulnerable Jackson version. Runtime versions and dependency versions are separate risk surfaces — the latter is what software composition analysis exists for, and a scanner such as Safeguard will flag both an outdated dependency line and the known CVEs attached to it long before a pentest would.
Troubleshooting quick hits
java: command not foundafter install — keg-only strikes again: either add the symlink (step 2) and set PATH viaJAVA_HOME, or follow brew's caveat to prepend the keg's bin directory to PATH.java_homeshows the wrong version first — it lists the highest version by default; pin explicitly with-v.- "The operation couldn't be completed. Unable to locate a Java Runtime." — macOS's stub
/usr/bin/javafound no registered JDK; the symlink is missing. - Apple Silicon vs Intel paths — Homebrew lives at
/opt/homebrewon Apple Silicon and/usr/localon Intel. Copy-pasted snippets fail across architectures for exactly this reason;$(brew --prefix)writes portable scripts. - Rosetta duplication — an x86_64 brew under Rosetta plus a native arm64 brew means two independent JDK sets. Check
archand which brew is first in PATH.
Uninstalling cleanly
brew uninstall openjdk@21
sudo rm /Library/Java/JavaVirtualMachines/openjdk-21.jdk # remove your symlink
/usr/libexec/java_home -V # verify it is gone
Old installer-based JDKs from previous jobs and tutorials tend to lurk in /Library/Java/JavaVirtualMachines — while you are in there, delete anything you no longer need. Every stale JDK is an unpatched runtime someone might accidentally target a build at.
FAQ
What is the single command to install Java on a Mac with Homebrew?
brew install openjdk for the latest OpenJDK, or brew install openjdk@21 for the current LTS. Follow it with the symlink command brew prints in its caveats so /usr/libexec/java_home can find the JDK, then set JAVA_HOME.
Should I install openjdk or Temurin via brew?
Both are solid OpenJDK builds. Homebrew's openjdk formulae are keg-only and need a manual symlink; the temurin cask installs a vendor .pkg that macOS registers automatically. Teams standardizing on a supported distribution often pick Temurin; either is fine for development.
Why does java -version fail right after brew install openjdk?
Because the formula is keg-only: Homebrew deliberately does not link it into your PATH or register it with macOS. Add the symlink into /Library/Java/JavaVirtualMachines and export JAVA_HOME from /usr/libexec/java_home, and it resolves.
How do I update Java installed with brew?
brew update && brew upgrade. Version-pinned formulae like openjdk@21 receive patch updates within their line, which is exactly what you want for the quarterly JDK security releases.