Java Version Managers
Compare macOS Java version managers SDKMAN, mise, asdf, jEnv, Jabba. Simple JDK version switching with JAVA_HOME. Switch between multiple Java versions on Mac globally or per project.
If your work requires different Java versions for different projects on a Mac, you can use a Java version manager. Tools like jenv and SDKMAN make switching between versions simple. This guide compares six approaches to managing multiple Java versions on Macs: SDKMAN, jEnv, Jabba, mise, asdf, and a manual method using macOS's built-in java_home utility.
Here's why you may need a version manager: A legacy application might require Java 8, while modern development uses Java 25. Switching between JDK versions can be tedious and error-prone, by editing shell configuration files, setting JAVA_HOME each time, and restarting the terminal after changes. Version managers solve this problem by handling the switching automatically.
Before you get started
You'll need a terminal application to develop with Java. Apple includes the Mac terminal but I prefer Warp Terminal. Warp is an easy-to-use terminal application, with AI assistance to help you learn and remember terminal commands. Download Warp Terminal now; it's FREE and makes coding easier when working with Java.
Choosing a Java Version Manager
Here are the most popular and recommended Java version managers.
- SDKMAN – JVM-focused (Java, Kotlin, Scala), installs JDKs plus Maven, Gradle, Spring Boot CLI
- mise – Polyglot (Java + Node + Python + Ruby), fastest performance, unified config, automatic JAVA_HOME
- jEnv – Homebrew-centric workflow, lightweight switching for Homebrew-installed JDKs
- Manual method – Simple needs (1–2 JDKs), no tools required, uses built-in macOS utility
Most Java developers will use SDKMAN. It installs JDKs automatically, manages JAVA_HOME without extra configuration, and handles Maven and Gradle alongside your JDKs. Developers who use multiple programming languages such as Node.js, Python, or Ruby should use mise. It manages all languages through one tool with the fastest shell performance available.
Understanding JAVA_HOME and PATH
Two environment variables control which Java version is available by default.
- JAVA_HOME – Tools like Maven and Gradle read this variable to find the Java compiler.
- PATH – When you type
java, your shell searches directories in PATH to find the binary. Apple's macOS includes a built-in Java system launcher in the default/usr/binpath that launches the newest Java version from a default directory but you can override this Java default by setting$PATH.
Version managers work by updating these variables when you switch Java versions. Some tools handle this automatically; others require extra configuration. Read Set JAVA_HOME on Mac for detailed environment variable configuration. Read Java PATH on Mac for PATH troubleshooting.
What JDKs Do You Have Installed?
See the article Check Java Version on Mac to examine what JDKs are installed on your Mac.
SDKMAN (Recommended for Java-only Developers)
SDKMAN (Software Development Kit Manager) is the most popular Java version manager. It installs and manages JDK versions from multiple vendors and handles JVM ecosystem tools like Maven, Gradle, Kotlin, and Spring Boot CLI. It's popular as an all-purpose tool for everything in the JVM ecosystem, but better for developers who only use Java and no other languages.
SDKMAN Advantages
- Installs JDKs automatically from multiple vendors (Temurin, Zulu, Corretto, GraalVM)
- Manages developer tools Maven, Gradle, Kotlin, and Spring Boot CLI
- Automatic JAVA_HOME management with no extra configuration
.sdkmanrcenables automatic per-project version switching
SDKMAN Disadvantages
- Not installed via Homebrew (manages its own directory at
~/.sdkman) - Adds 30-300ms to shell startup time
- Java-specific only (need separate tools for Node.js, Python, etc.)
- Automatic initialization configures
PATHduring installation
Quick Install
Learn How to Open Terminal in Mac and run:
$ curl -s "https://get.sdkman.io" | bash
The installation script will automatically configure PATH so SDKMAN overrides the Java system launcher or any JDK previously installed.
SDKMAN installs JDKs to its own directory:
$ sdk install java 25.0.1-tem
Lazy Loading for Performance
SDKMAN adds 30-300ms to shell startup. If this bothers you, use lazy loading so the tool initializes only when you first run an sdk command. Add to your ~/.zshrc file:
sdk() {
unset -f sdk
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk "$@"
}
SDKMAN Basic Usage
$ sdk list java # List available JDK distributions
$ sdk use java 17.0.12-zulu # Switch for current session
$ sdk default java 25.0.1-tem # Set permanent default
Read Install SDKMAN for Java on Mac for complete SDKMAN coverage including project configuration, managing build tools, and offline mode.
mise (Recommended for Multiple Languages)
Mise (pronounced "MEEZ," from French "mise-en-place") manages multiple languages through a single tool. It handles Java, Node.js, Python, Ruby, Go, and dozens of others, all with identical commands. For developers working across multiple languages, mise replaces separate version managers with one unified solution.
Mise is written in Rust and runs significantly faster than shell-based alternatives. Shell operations complete in about 10ms, compared to 50-300ms for tools like SDKMAN or asdf.
mise Advantages
- One tool for all languages (Java, Node.js, Python, Ruby, Go, etc.)
- Fastest performance (~10ms vs 200ms for asdf)
- Automatic JAVA_HOME management
- Built-in environment variable management
- Compatible with asdf plugins and
.tool-versionsfiles - Active development and growing community (21,000+ GitHub stars)
mise Disadvantages
- Requires
mise trustfor security (extra step when cloning projects) - Less JVM ecosystem coverage (no built-in Maven/Gradle management like SDKMAN)
- Newer tool with less established history than SDKMAN
Install mise
Install mise via Homebrew:
$ brew install mise
Edit the ~/.zshrc shell configuration file and add:
eval "$(mise activate zsh)"
You'll need to run source ~/.zshrc or close and reopen your terminal.
See Shell Configuration and setting the Mac Path for background and details.
Install Java Versions
List available Java versions:
$ mise ls-remote java
Install Eclipse Temurin 25 (recommended distribution and version):
$ mise install java@temurin-25
Set it as your global default:
$ mise use -g java@temurin-25
Install additional versions:
$ mise install java@temurin-17
$ mise install java@zulu-8
Mise automatically sets JAVA_HOME when you switch versions. No extra configuration is needed, which is a major advantage over asdf, which requires manual JAVA_HOME setup.
Project Configuration
Set a project-specific Java version:
$ cd your-project
$ mise use java@temurin-17
This creates a mise.toml file in your project directory:
[tools]
java = "temurin-17"
Mise automatically switches to this version when you enter the directory. Commit mise.toml to git and an entire team will use the same Java version.
The Trust Model
For security, mise requires explicit trust before applying project configurations. When you enter a directory with a new mise.toml, run:
$ mise trust
This adds friction to a development workflow but it is required to prevent malicious configuration files from automatically executing when you clone untrusted repositories.
Environment Variables
mMise handles more than tool versions. Add project-specific environment variables to mise.toml:
[tools]
java = "temurin-17"
node = "22"
[env]
JAVA_OPTS = "-Xmx2g"
DATABASE_URL = "postgres://localhost/mydb"
This eliminates separate .env files for many use cases.
Compatibility with .tool-versions
Mise reads asdf's .tool-versions format for backward compatibility:
java temurin-25
nodejs 22.0.0
python 3.12.0
If you're migrating from asdf, your existing configuration files work without changes.
jEnv (For Homebrew-Managed JDKs)
jEnv is a lightweight version manager inspired by rbenv for Ruby. Unlike SDKMAN and mise, jEnv does not install JDKs. It will manage JDKs you have already installed elsewhere, typically via Homebrew. This separation appeals to developers who prefer Homebrew for package management and want minimal additional tooling.
jEnv Advantages
- Lightweight and simple concept
- Works with Homebrew-installed JDKs
- Familiar commands for rbenv or pyenv users
.java-versionfile for per-project configuration
jEnv Disadvantages
- Does not install JDKs (requires separate installation)
- JAVA_HOME plugin must be enabled manually
- Requires
jenv rehashafter adding JDKs - Less active development than SDKMAN or mise
- Shim-based architecture adds some overhead to every Java command
Install jEnv
Install via Homebrew:
$ brew install jenv
Edit the ~/.zshrc shell configuration file and add:
export PATH="$HOME/.jenv/bin:$PATH"
eval "$(jenv init -)"
You'll need to run source ~/.zshrc or close and reopen your terminal.
See Shell Configuration and setting the Mac Path for background and details.
Install JDKs via Homebrew
jEnv manages existing JDKs, so install them with Homebrew casks if they are not already installed:
$ brew install --cask temurin@17
$ brew install --cask temurin@25
Homebrew casks install JDKs to /Library/Java/JavaVirtualMachines/, the standard macOS location.
Read Brew Install Java - Easy Cask Method for detailed Homebrew installation options.
Register JDKs with jEnv
After installing JDKs, register each one:
$ jenv add /Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home/
$ jenv add /Library/Java/JavaVirtualMachines/temurin-25.jdk/Contents/Home/
List registered versions:
$ jenv versions
Critical: Run Rehash
After adding JDKs, you must update jEnv's shims:
$ jenv rehash
Forgetting this step is a common source of "command not found" errors. Run jenv rehash whenever you add or remove JDKs.
Critical: Enable the JAVA_HOME Plugin
By default, jEnv does NOT set JAVA_HOME. This causes Maven, Gradle, and most IDEs to fail. Enable the export plugin:
$ jenv enable-plugin export
You'll need to run source ~/.zshrc or close and reopen your terminal.
Verify JAVA_HOME is now set:
$ echo $JAVA_HOME
This step trips up many new users. Without it, your build tools won't find Java even though the java command works.
Switch Between Versions
Set a global default:
$ jenv global 21
Set a project-specific version (creates a .java-version file):
$ cd your-project
$ jenv local 17
Set a version for the current shell session only:
$ jenv shell 17
Check the active version:
$ jenv version
jEnv is a good choice if you use similar tools like rbenv or pyenv.
Jabba (Cross-Platform Manager)
Jabba is a Java version manager written in Go and distributed as a single binary. It was inspired by nvm (the Node.js version manager) and works across macOS, Linux, and Windows. This cross-platform consistency appeals to teams working on multiple operating systems.
Important: Use the Community Fork
The original Jabba repository ('shyiko/jabba') has been abandoned since 2019. The active community fork at Jabba-Team/jabba continues development and properly supports Apple Silicon. Always use the Jabba-Team fork.
Jabba Disadvantages
Jabba does not automatically switch versions when entering project directories. You must manually run jabba use each time. This makes it less convenient than SDKMAN or mise for multi-project workflows.
Jabba is supported by a small community and receives fewer updates and gets less documentation than SDKMAN. For macOS-only teams, SDKMAN or mise are better choices. Jabba makes sense primarily for cross-platform teams needing identical tooling on Mac and Windows machines.
Install Jabba
Install via Homebrew:
$ brew install jabba
Edit the ~/.zshrc shell configuration file and add:
[ -s "$HOMEBREW_PREFIX/opt/jabba/jabba.sh" ] && . "$HOMEBREW_PREFIX/opt/jabba/jabba.sh"
You'll need to run source ~/.zshrc or close and reopen your terminal.
See Shell Configuration and setting the Mac Path for background and details.
Install Java Versions
List available versions:
$ jabba ls-remote
Install a version:
$ jabba install temurin@25
Switch to a version:
$ jabba use temurin@25
Set a default:
$ jabba alias default temurin@25
Jabba is best for cross-platform teams who use Mac and Windows machines.
asdf (Universal Version Manager)
asdf manages multiple languages through plugins. You install one tool, then add plugins for each language you need. For developers who use multiple languages, asdf provides a consistent interface across Java, Node.js, Ruby, Python, and dozens of other languages.
However, asdf is slower than the newer and faster mise and requires more configuration for Java. If you're starting fresh, mise is the better choice. This section covers asdf for developers who already use it for other languages and want to add Java.
asdf Advantages
- One tool for all languages
- Consistent interface across languages
- per-project configuration with
.tool-versions - Growing plugin ecosystem
asdf Disadvantages
- Slower than mise (50-150ms vs 10ms shell overhead)
- JAVA_HOME requires manual configuration
- Plugin-based architecture adds complexity
- Less JVM ecosystem coverage than SDKMAN
Install asdf
Install via Homebrew:
$ brew install asdf
Add to ~/.zshrc:
export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH"
You'll need to run source ~/.zshrc or close and reopen your terminal.
Install the Java Plugin
$ asdf plugin add java
Configure JAVA_HOME
Unlike SDKMAN and mise, asdf requires manual JAVA_HOME configuration. Add to ~/.zshrc:
. ~/.asdf/plugins/java/set-java-home.zsh
You'll need to run source ~/.zshrc or close and reopen your terminal.
Install Java Versions
List available versions:
$ asdf list all java
Filter by distribution:
$ asdf list all java | grep temurin
Install a version:
$ asdf install java temurin-25.0.4+7.0.LTS
Set global default:
$ asdf global java temurin-25.0.4+7.0.LTS
Set project-specific version:
$ cd your-project
$ asdf local java temurin-17.0.12+7
The .tool-versions File
asdf creates .tool-versions files for per-project configuration:
java temurin-25.0.4+7.0.LTS
nodejs 22.0.0
ruby 3.3.0
python 3.12.0
Commit this file to git. Everyone on your team gets identical Java versions.
Legacy Version File Support
asdf can read jEnv's .java-version files. Add to ~/.asdfrc:
legacy_version_file = yes
This helps when migrating from jEnv without changing existing project configurations.
Manual Method (For Simple Needs)
If you only use one or two Java versions and rarely switch, a version manager might be overkill. macOS includes the /usr/libexec/java_home utility for finding installed JDKs. Combined with shell aliases, this provides basic version switching without additional tools.
Manual Switching Advantages
This approach suits developers who use a single Java version most of the time, work in environments where installing additional tools is restricted, or want to understand Java configuration before adding complexity.
Manual Switching Disadvantages
Use a version manager if you switch versions frequently, work on multiple projects with different Java requirements, need automatic per-project version switching, or want team members to use identical versions without coordination.
List Installed JDKs
The java_home utility discovers JDKs in /Library/Java/JavaVirtualMachines/:
$ /usr/libexec/java_home -V
This lists all installed JDKs with their versions and paths.
Set JAVA_HOME
Get the path for a specific version:
$ /usr/libexec/java_home -v 21
Set JAVA_HOME in ~/.zprofile or ~/.zshrc:
export JAVA_HOME=$(/usr/libexec/java_home -v 21)
Read Set JAVA_HOME on Mac for detailed environment variable configuration.
Create Shell Aliases
Add switching aliases to ~/.zshrc:
alias java17='export JAVA_HOME=$(/usr/libexec/java_home -v 17)'
alias java21='export JAVA_HOME=$(/usr/libexec/java_home -v 21)'
Or create a function for any version:
jdk() {
export JAVA_HOME=$(/usr/libexec/java_home -v "$1")
java -version
}
Usage:
$ jdk 17
$ jdk 21
Shell Configuration
Each version manager requires shell integration. Here's what goes in ~/.zshrc for each tool. See Shell Configuration for details.
SDKMAN
SDKMAN adds this automatically during installation:
export SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"
You may want to check your ~/.zshrc file to see how SDKMAN automatically modified the file.
mise
You need to add:
eval "$(mise activate zsh)"
jEnv
You need to add:
export PATH="$HOME/.jenv/bin:$PATH"
eval "$(jenv init -)"
asdf
You need to add:
export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH"
. ~/.asdf/plugins/java/set-java-home.zsh
Measuring Shell Startup Time
Check how long your shell takes to start:
$ time zsh -i -c exit
Run this several times and average the results. If startup exceeds 500ms, consider lazy loading or switching to mise for faster initialization.
Avoid Running Multiple Version Managers
Don't initialize multiple Java version managers in your shell. They conflict with each other. Pick one tool and remove initialization for others from ~/.zshrc.
IDE Integration
IntelliJ IDEA
IntelliJ auto-detects JDKs from /Library/Java/JavaVirtualMachines/. For JDKs installed by version managers in non-standard locations, add them manually:
- Open File → Project Structure → SDKs
- Click the + button
- Navigate to the JDK path
For SDKMAN-managed JDKs, point to ~/.sdkman/candidates/java/25.0.1-tem (or whichever version you need).
IntelliJ's project SDK setting is separate from your shell's JAVA_HOME. Changing one doesn't affect the other. Configure project SDK via File → Project Structure → Project.
VS Code
The Extension Pack for Java detects JDKs from JAVA_HOME, system PATH, and common installation locations. Configure additional runtimes in settings.json:
{
"java.configuration.runtimes": [
{
"name": "JavaSE-17",
"path": "/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home"
},
{
"name": "JavaSE-21",
"path": "/Library/Java/JavaVirtualMachines/temurin-25.jdk/Contents/Home",
"default": true
}
]
}
General IDE Guidance
Most IDEs read JAVA_HOME at startup. If you switch Java versions in the terminal, restart your IDE for it to pick up the change. Some IDEs cache the Java path and require explicit reconfiguration.
Troubleshooting
"java: command not found" After Installation
For SDKMAN, open a new Terminal or source the init script:
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
For jEnv, ensure shell integration is in ~/.zshrc.
You'll need to run source ~/.zshrc or close and reopen your terminal.
For mise, run mise trust if using local configuration.
JAVA_HOME Not Set
For jEnv, enable the export plugin:
$ jenv enable-plugin export
You'll need to run source ~/.zshrc or close and reopen your terminal.
For asdf, add to ~/.zshrc:
. ~/.asdf/plugins/java/set-java-home.zsh
jEnv Not Recognizing Installed JDK
Run rehash:
$ jenv rehash
Verify the JDK was added:
$ jenv versions
mise Configuration Not Applying
Run trust in the project directory:
$ mise trust
Check active configuration:
$ mise current
Version Manager Conflicts
If switching between version managers:
- Remove the old tool's configuration from
~/.zshrc - Open a new terminal
- Verify with
which javathat the expected tool manages Java
SDKMAN and Homebrew Conflicts
SDKMAN and Homebrew can coexist, but avoid installing Java via both. If you use SDKMAN, let it manage all your JDKs. Mixing installation methods causes confusion about which Java is active.
Java Version Manager Comparison
Feature support:
- Installs JDKs – SDKMAN ✅, mise ✅, jEnv ❌, Jabba ✅, asdf ✅, Manual ❌
- JAVA_HOME auto-set – SDKMAN ✅, mise ✅, jEnv (plugin needed), Jabba ✅, asdf (config needed), Manual (manual)
- Maven/Gradle – SDKMAN ✅, mise (via plugins), jEnv ❌, Jabba ❌, asdf (via plugins), Manual ❌
- Multi-language – SDKMAN (JVM only), mise ✅, jEnv (Java only), Jabba (Java only), asdf ✅, Manual (Java only)
- Auto directory switch – SDKMAN ✅, mise ✅, jEnv ✅, Jabba ❌, asdf ✅, Manual ❌
- Shell startup impact – SDKMAN (30–300ms), mise (~10ms), jEnv (~50ms), Jabba (~10ms), asdf (50–150ms), Manual (none)
- Config file – SDKMAN (
.sdkmanrc), mise (mise.toml), jEnv (.java-version), Jabba (.jabbarc), asdf (.tool-versions), Manual (none) - Homebrew install – SDKMAN ❌, mise ✅, jEnv ✅, Jabba ✅, asdf ✅, Manual (N/A)
Final Recommendations
For most Java developers: Use SDKMAN. It installs JDKs, manages JAVA_HOME automatically, and handles Maven and Gradle. One tool covers your entire JVM ecosystem.
For multiple languages: Use mise. It manages Java alongside Node.js, Python, and Ruby with the fastest shell performance. The unified .tool-versions format works across all languages.
For Homebrew users who want minimal tooling: Use jEnv. It adds lightweight version switching to your existing Homebrew-installed JDKs. Just remember to enable the export plugin and run rehash.
For simple setups: Use the manual method with shell aliases. It works without installing anything and suffices if you rarely switch versions.
What's Next
My mac.install.guide is a trusted source of installation guides for professional developers. Take a look at the Mac Install Guide home page for tips and trends and see what to install next.