A One liner to create a symlink from macOS's expected Java location to SDKMAN's installation:
curl -fsSL https://gist.githubusercontent.com/abd3lraouf/1db9bf863144802733bfd29bb5dada87/raw/install.sh | bash -s install
This creates /Library/Java/JavaVirtualMachines/sdkman-current/ pointing to ~/.sdkman/candidates/java/current, allowing /usr/libexec/java_home and Xcode to find your SDKMAN Java installation.
When you switch Java versions with sdk use java [version], the symlink automatically points to the new version. Full script and manual setup instructions.Retry
Gist: https://gist.github.com/abd3lraouf/1db9bf863144802733bfd29bb5dada87
Full script here:
#!/usr/bin/env bash
################################################################################
# Setup SDKMAN JDK Integration with /usr/libexec/java_home
################################################################################
# This script configures macOS to recognize SDKMAN-installed JDKs through
# /usr/libexec/java_home by creating a proper directory structure and symlink
# in /Library/Java/JavaVirtualMachines.
#
# Usage:
# ./setup_sdkman_java_home.sh [install|uninstall|verify|help]
#
# Options:
# install - Set up the SDKMAN JDK integration (default)
# uninstall - Remove the SDKMAN JDK integration
# verify - Check if the setup is working correctly
# help - Display this help message
#
# Requirements:
# - macOS
# - SDKMAN installed with at least one JDK
# - sudo privileges
################################################################################
set -eo pipefail
# Constants (temporarily disable -u for BASH_SOURCE check)
readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]:-install.sh}")"
readonly SCRIPT_VERSION="1.0.0"
# Re-enable unbound variable checking
set -u
readonly TARGET_DIR="/Library/Java/JavaVirtualMachines/sdkman-current"
readonly CONTENTS_DIR="${TARGET_DIR}/Contents"
readonly HOME_LINK="${CONTENTS_DIR}/Home"
readonly PLIST_FILE="${CONTENTS_DIR}/Info.plist"
# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly BOLD='\033[1m'
readonly NC='\033[0m' # No Color
# Cleanup on error - silently exit (specific errors are already shown)
cleanup() {
local exit_code=$?
# Exit silently - specific error messages are already displayed
exit $exit_code
}
trap cleanup EXIT
################################################################################
# Utility Functions
################################################################################
# Print colored messages
print_header() {
echo -e "\n${BOLD}${BLUE}===${NC} ${BOLD}$1${NC} ${BOLD}${BLUE}===${NC}\n"
}
success() {
echo -e "${GREEN}✓${NC} $1"
}
info() {
echo -e "${BLUE}ℹ${NC} $1"
}
warn() {
echo -e "${YELLOW}⚠${NC} $1"
}
error() {
echo -e "${RED}✗${NC} $1" >&2
}
# Check if running on macOS
check_macos() {
if [[ "$(uname -s)" != "Darwin" ]]; then
error "This script only works on macOS"
exit 1
fi
}
# Check if SDKMAN is installed
check_sdkman() {
local sdkman_dir="${SDKMAN_DIR:-$HOME/.sdkman}"
if [[ ! -d "$sdkman_dir" ]]; then
error "SDKMAN not found at $sdkman_dir"
info "Install SDKMAN from: https://sdkman.io/install"
exit 1
fi
success "SDKMAN found at $sdkman_dir"
}
# Get SDKMAN current JDK path
get_sdkman_current_jdk() {
local sdkman_dir="${SDKMAN_DIR:-$HOME/.sdkman}"
local current_jdk="${sdkman_dir}/candidates/java/current"
if [[ ! -e "$current_jdk" ]]; then
error "No current JDK set in SDKMAN"
info "Install a JDK with: sdk install java"
info "Or set current with: sdk use java <version>"
exit 1
fi
# Resolve symlink to get actual JDK path
local actual_jdk
actual_jdk="$(readlink "$current_jdk" 2>/dev/null || echo "$current_jdk")"
echo "$current_jdk"
}
# Get JDK version from SDKMAN
get_jdk_version() {
local jdk_path="$1"
# Source SDKMAN and get current version
local sdkman_init="${SDKMAN_DIR:-$HOME/.sdkman}/bin/sdkman-init.sh"
if [[ -f "$sdkman_init" ]]; then
# Temporarily disable -u for sourcing SDKMAN and calling sdk (they have undefined variables)
set +u
# shellcheck source=/dev/null
source "$sdkman_init" 2>/dev/null || true
local version
version="$(sdk current java 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+[^[:space:]]*' || echo "unknown")"
set -u
echo "$version"
else
echo "unknown"
fi
}
# Get JDK vendor from path
get_jdk_vendor() {
local jdk_path="$1"
local vendor="SDKMAN"
# Extract vendor from path if possible (e.g., 21.0.9-amzn -> Amazon)
if [[ "$jdk_path" =~ -([a-z]+)$ ]]; then
local vendor_code="${BASH_REMATCH[1]}"
case "$vendor_code" in
amzn) vendor="Amazon Corretto" ;;
tem) vendor="Eclipse Temurin" ;;
zulu) vendor="Azul Zulu" ;;
graal*) vendor="GraalVM" ;;
liberica) vendor="BellSoft Liberica" ;;
sapmchn) vendor="SAP Machine" ;;
*) vendor="SDKMAN ($vendor_code)" ;;
esac
fi
echo "$vendor"
}
# Check if setup already exists
check_existing_setup() {
[[ -d "$TARGET_DIR" ]]
}
# Verify sudo access
verify_sudo() {
# Try non-interactive sudo first
if sudo -n true 2>/dev/null; then
# Already authenticated
# Keep sudo alive
while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null &
return 0
fi
# Check if running non-interactively
if ! [ -t 0 ]; then
error "This script requires sudo access"
echo ""
echo "When running via pipe, sudo cannot prompt for password."
echo "Please authenticate sudo first, then re-run:"
echo ""
echo " sudo -v"
echo " curl -fsSL https://gist.githubusercontent.com/abd3lraouf/1db9bf863144802733bfd29bb5dada87/raw/install.sh | bash -s install"
echo ""
echo "Or download and run directly:"
echo ""
echo " curl -O https://gist.githubusercontent.com/abd3lraouf/1db9bf863144802733bfd29bb5dada87/raw/install.sh"
echo " chmod +x install.sh"
echo " ./install.sh install"
exit 1
fi
# Interactive mode - can prompt
if ! sudo -v; then
error "Sudo privileges required"
exit 1
fi
# Keep sudo alive
while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null &
}
################################################################################
# Installation Functions
################################################################################
install_setup() {
print_header "Installing SDKMAN JDK Integration"
# Get SDKMAN JDK path
local sdkman_jdk
sdkman_jdk="$(get_sdkman_current_jdk)"
local jdk_version
jdk_version="$(get_jdk_version "$sdkman_jdk")"
local jdk_vendor
jdk_vendor="$(get_jdk_vendor "$sdkman_jdk")"
info "SDKMAN Current JDK: $sdkman_jdk"
info "Version: $jdk_version"
info "Vendor: $jdk_vendor"
# Check for existing setup
if check_existing_setup; then
warn "Existing setup found at $TARGET_DIR"
# Check if running interactively (can prompt)
if [ -t 0 ]; then
read -rp "Remove and reinstall? (y/N): " response
if [[ "$response" =~ ^[Yy]$ ]]; then
uninstall_setup
else
info "Installation cancelled"
exit 0
fi
else
# Non-interactive: auto-reinstall
info "Non-interactive mode: auto-reinstalling..."
uninstall_setup
fi
fi
# Request sudo access
verify_sudo
# Create directory structure
info "Creating directory structure..."
sudo mkdir -p "$CONTENTS_DIR"
success "Created $CONTENTS_DIR"
# Create symlink
info "Creating symlink to SDKMAN JDK..."
sudo ln -sf "$sdkman_jdk" "$HOME_LINK"
success "Created symlink: $HOME_LINK -> $sdkman_jdk"
# Create Info.plist
info "Creating Info.plist..."
sudo tee "$PLIST_FILE" > /dev/null << EOF
<?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>CFBundleVersion</key>
<string>${jdk_version}</string>
<key>JavaVM</key>
<dict>
<key>JVMPlatformVersion</key>
<string>9999</string>
<key>JVMVendor</key>
<string>${jdk_vendor}</string>
<key>JVMVersion</key>
<string>9999</string>
</dict>
</dict>
</plist>
EOF
success "Created Info.plist"
# Set proper permissions
info "Setting permissions..."
sudo chmod -R 755 "$TARGET_DIR"
success "Permissions set"
print_header "Installation Complete"
success "SDKMAN JDK integration installed successfully"
# Verify installation
verify_setup
}
################################################################################
# Uninstallation Functions
################################################################################
uninstall_setup() {
print_header "Uninstalling SDKMAN JDK Integration"
if ! check_existing_setup; then
warn "No existing setup found at $TARGET_DIR"
info "Nothing to uninstall"
return 0
fi
# Request sudo access if not already verified
verify_sudo
info "Removing $TARGET_DIR..."
sudo rm -rf "$TARGET_DIR"
success "Removed SDKMAN JDK integration"
print_header "Uninstallation Complete"
}
################################################################################
# Verification Functions
################################################################################
verify_setup() {
print_header "Verifying Setup"
# Check if setup exists
if ! check_existing_setup; then
error "Setup not found at $TARGET_DIR"
info "Run with 'install' to set up the integration"
exit 1
fi
# Check symlink
if [[ ! -L "$HOME_LINK" ]]; then
error "Symlink not found at $HOME_LINK"
exit 1
fi
success "Symlink exists: $HOME_LINK"
# Check if symlink target exists
if [[ ! -e "$HOME_LINK" ]]; then
error "Symlink target does not exist"
local target
target="$(readlink "$HOME_LINK")"
error "Broken symlink: $HOME_LINK -> $target"
exit 1
fi
local symlink_target
symlink_target="$(readlink "$HOME_LINK")"
success "Symlink target valid: $symlink_target"
# Check Info.plist
if [[ ! -f "$PLIST_FILE" ]]; then
error "Info.plist not found at $PLIST_FILE"
exit 1
fi
success "Info.plist exists"
# Test /usr/libexec/java_home
info "Testing /usr/libexec/java_home..."
local java_home_output
if java_home_output=$(/usr/libexec/java_home 2>&1); then
success "/usr/libexec/java_home works"
echo -e "\n${BOLD}Output:${NC}"
echo "$java_home_output"
else
error "/usr/libexec/java_home failed"
echo "$java_home_output"
exit 1
fi
# Show all available JVMs
echo -e "\n${BOLD}Available Java Virtual Machines:${NC}"
/usr/libexec/java_home -V 2>&1 || true
print_header "Verification Complete"
success "Setup is working correctly"
echo
info "Notes:"
echo " • When you change JDK with 'sdk use java <version>', the symlink"
echo " will automatically point to the new current version"
echo " • Xcode and other tools can now find Java using /usr/libexec/java_home"
echo " • The version is set to 9999 to ensure this JDK is always selected"
}
################################################################################
# Help Function
################################################################################
show_help() {
cat << 'EOF'
install.sh - Version 1.0.0
DESCRIPTION
Configure macOS to recognize SDKMAN-installed JDKs through /usr/libexec/java_home
by creating a proper directory structure in /Library/Java/JavaVirtualMachines.
USAGE
install.sh [COMMAND]
COMMANDS
install Set up the SDKMAN JDK integration (default)
uninstall Remove the SDKMAN JDK integration
verify Verify the setup is working correctly
help Display this help message
EXAMPLES
# Install the integration
install.sh install
# Verify it's working
install.sh verify
# Remove the integration
install.sh uninstall
HOW IT WORKS
1. Creates /Library/Java/JavaVirtualMachines/sdkman-current/Contents/
2. Symlinks Contents/Home to SDKMAN's current JDK
3. Creates Info.plist with proper metadata
4. Sets version to 9999 to ensure it's always selected
REQUIREMENTS
- macOS
- SDKMAN installed with at least one JDK
- sudo privileges
FILES CREATED
/Library/Java/JavaVirtualMachines/sdkman-current/
/Library/Java/JavaVirtualMachines/sdkman-current/Contents/
/Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home (symlink)
/Library/Java/JavaVirtualMachines/sdkman-current/Contents/Info.plist
MORE INFO
- SDKMAN: https://sdkman.io/
- Gist: https://gist.github.com/abd3lraouf/1db9bf863144802733bfd29bb5dada87
EOF
}
################################################################################
# Main Function
################################################################################
main() {
# Check prerequisites
check_macos
# Parse command
local command="${1:-install}"
case "$command" in
install)
check_sdkman
install_setup
;;
uninstall)
uninstall_setup
;;
verify)
check_sdkman
verify_setup
;;
help|--help|-h)
show_help
;;
*)
error "Unknown command: $command"
echo
show_help
exit 1
;;
esac
}
# Run main function
main "$@"