Making SDKMAN Java Work with macOS: A One-Line Fix
If you've ever tried building a Kotlin Multiplatform project in Xcode with a SDKMAN-installed JDK, you've probably seen this frustrating error:
The operation couldn't be completed. Unable to locate a Java Runtime.
Please visit http://www.java.com for information on installing Java.Your Java works perfectly in the terminal. java -version shows exactly what you expect. But Xcode? It's completely blind to your installation.
The Problem
This issue surfaced when I was working on iOS apps with Kotlin, which relies on Gradle being called from Xcode Script Build Phases. The root cause is a fundamental mismatch in how SDKMAN and macOS handle Java installations.
SDKMAN installs JDKs to:
~/.sdkman/candidates/java/But macOS looks for JDKs in:
/Library/Java/JavaVirtualMachines/The key player here is /usr/libexec/java_home - a mostly undocumented macOS utility that tools like Xcode use to locate Java installations. It completely ignores JAVA_HOME environment variables and only looks for JDKs in the standard macOS location with a specific directory structure.
Credit goes to Ribesg who discovered and documented this solution on StackOverflow. Their answer has helped thousands of developers facing the same issue. I've automated their manual process into a one-line installer.
The Manual Solution (Thanks Ribesg!)
Ribesg figured out that /usr/libexec/java_home expects this specific structure:
jdk-root-folder/
Contents/
Info.plist
Home/
<actual JDK files>The fix is to create a "fake" JDK structure that symlinks to your SDKMAN installation:
- Create
/Library/Java/JavaVirtualMachines/sdkman-current/Contents/ - Symlink
Contents/Hometo~/.sdkman/candidates/java/current - Add an
Info.plistwith proper metadata
The clever part: by setting the version to 9999 in the plist, this JDK always becomes the default when multiple versions exist.
The One-Line Fix
Manual setup is error-prone and tedious. So I automated it:
curl -fsSL https://gist.githubusercontent.com/abd3lraouf/1db9bf863144802733bfd29bb5dada87/raw/install.sh | bash -s installThis script:
- Creates the proper directory structure in
/Library/Java/JavaVirtualMachines/ - Sets up a symlink to your SDKMAN current JDK
- Generates the required
Info.plistwith correct metadata - Verifies everything works
The best part? When you switch Java versions with sdk use java 17, the integration automatically follows because it points to SDKMAN's current symlink.
Verification
After running the script, verify it works:
/usr/libexec/java_home
# Output: /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home
/usr/libexec/java_home -V
# Shows your SDKMAN JDK in the listNow Xcode build phases, VSCode Java extensions, and any tool relying on /usr/libexec/java_home will find your SDKMAN installation.
Under the Hood
The script creates this structure:
/Library/Java/JavaVirtualMachines/sdkman-current/
├── Contents/
│ ├── Home → ~/.sdkman/candidates/java/current
│ └── Info.plistThe Info.plist contains:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "...">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>sdkman.current</string>
<key>CFBundleName</key>
<string>SDKMAN Current JDK</string>
<key>JavaVM</key>
<dict>
<key>JVMVersion</key>
<string>9999</string>
</dict>
</dict>
</plist>Other Commands
The script supports more than just installation:
# Verify the setup is working
./install.sh verify
# Clean removal
./install.sh uninstall
# Show help
./install.sh helpWhy This Matters
This isn't just about Xcode. Many tools rely on /usr/libexec/java_home:
- VSCode/Cursor Java extensions
- IntelliJ IDEA (sometimes)
- Gradle when not run from a shell with
JAVA_HOMEset - Android Studio in certain configurations
- Any GUI application using
JavaLauncher.app
If you're using SDKMAN (and you should be - it's the best way to manage Java versions), this one-line fix bridges the gap between SDKMAN's flexible version management and macOS's rigid expectations.
Resources
Stop fighting with Java installations. Run the one-liner and get back to building.