How to make /usr/libexec/java_home find JDK installed using SDKMAN on MacOS?
Asked Answered
K

1

7

I am making iOS apps in Kotlin, which relies on having a Script Build Phase calling Gradle from XCode. With a JDK installed using SDKMAN, it does not work and produces this error:

The operation couldn’t be completed. Unable to locate a Java Runtime.
Please visit http://www.java.com for information on installing Java.

The JDK installed with SDKMAN is working correctly on my system, SDKMAN sets JAVA_HOME to /Users/{user}/.sdkman/candidates/java/current by sourcing its sdkman-init.sh script from .bash_profile or .zshrc. But XCode Script Phase uses /bin/sh, and it does not seem to read these kinds of files.

I tried exporting JAVA_HOME in files like ~/.profile, /etc/profile, etc. with no result.

After some research, I found that this output is from a call to /usr/libexec/java_home producing no result. I also found that manually calling /usr/libexec/java_home inside a Terminal where JAVA_HOME is set correctly does not pick it up, so toying with JAVA_HOME is useless in this case: it just doesn't read it.

So, how do you make the /usr/libexec/java_home command find JDK installed using SDKMAN on MacOS?

Kursk answered 21/6, 2023 at 12:58 Comment(0)
K
15

The /usr/libexec/java_home seems to be mostly undocumented, it's very hard to find information about how it works. Combining the little information about it found online and some trial and error, I managed to trick /usr/libexec/java_home into returning SDKMAN's current JDK.

There are 2 issues that we need to work around:

  • SDKMAN-installed JDKs don't look like manually installed JDKs
  • SDKMAN-installed JDKs are not in a location /usr/libexec/java_home knows

On MacOS, manually installed JDKs are installed in /Library/Java/JavaVirtualMachines and look like this (non-exhaustive):

jdk-root-folder/
  Contents/
    Info.plist
    Home/
      <actual JDK files here>

But SDKMAN only installs the actual JDK files. The solution is to fake everything SDKMAN does not install:

  • Create a folder in /Library/Java/JavaVirtualMachines for your JDK, I'll call mine sdkman-current but the name does not matter.
  • Create a Contents folder inside sdkman-current
  • Create a symlink named Home inside the Contents folder, linking to your actual JDK:
    sudo ln -s /Users/{REPLACE_ME}/.sdkman/candidates/java/current /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home
    
  • Create a Info.plist file in the Contents folder with the following content:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <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>JVMPlatformVersion</key>
            <string>9999</string>
            <key>JVMVendor</key>
            <string>Homebrew</string>
            <key>JVMVersion</key>
            <string>9999</string>
        </dict>
    </dict>
    </plist>
    
    You can mostly use whatever <string> values you want in there, but note that removing any of the entries will prevent /usr/libexec/java_home from finding your JDK. I'm setting its version to 9999 so that its always the one returned by /usr/libexec/java_home.
  • Check that everything works:
    ➜  /usr/libexec/java_home
    /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home
    ➜  /usr/libexec/java_home -V
    Matching Java Virtual Machines (1):
        9999 (arm64) "Homebrew" - "SDKMAN Current JDK" /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home
    /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home
    

Now, stuff relying on /usr/libexec/java_home to find a JDK should work, including XCode Script Build Phase using /bin/sh.

Kursk answered 21/6, 2023 at 12:58 Comment(4)
This is also useful for people trying to open Java GUI applications using JavaLauncher.app with their installed JVM from SDKMAN! - This works like a charmIgnominious
Thank you so much, was trying to build a Kotlin Multiplatform project and ran in to the same issue as you. Was halfway towards removing SDKMAN and going back to manual installs.Boarder
I don't know why, but it not work for me in MacOS 10.15.7. Also, official Oracle JDK 22 has same issue.Demetri
@Demetri In my case I did it on MacOS 13 and it still work on MacOS 14, I have no idea how different it can be on MacOS 10. Please feel free to add an answer to this question if you find how to do it on MacOS 10!Kursk

© 2022 - 2024 — McMap. All rights reserved.