Skip to content

Instantly share code, notes, and snippets.

@abd3lraouf
Last active January 18, 2026 19:50
Show Gist options
  • Select an option

  • Save abd3lraouf/1db9bf863144802733bfd29bb5dada87 to your computer and use it in GitHub Desktop.

Select an option

Save abd3lraouf/1db9bf863144802733bfd29bb5dada87 to your computer and use it in GitHub Desktop.
SDKMAN Java Home Integration for macOS - Integrate SDKMAN-installed JDKs with /usr/libexec/java_home

SDKMAN Java Home Integration for macOS

A robust bash script to integrate SDKMAN-installed JDKs with macOS's /usr/libexec/java_home utility, enabling seamless Java detection for Xcode and other macOS development tools.

The Problem

/usr/libexec/java_home is macOS's standard way to locate Java installations, but it doesn't recognize SDKMAN-installed JDKs because:

  1. SDKMAN-installed JDKs don't follow the standard macOS JDK structure
  2. They're not located in /Library/Java/JavaVirtualMachines where macOS expects them

This causes issues with:

  • Xcode build phases that require Java
  • Tools relying on JAVA_HOME from /usr/libexec/java_home
  • Shell scripts expecting standard Java detection

The Solution

This script creates a properly structured symlink in /Library/Java/JavaVirtualMachines that points to SDKMAN's current JDK, complete with the required Info.plist metadata that /usr/libexec/java_home needs.

How It Works

  1. Creates /Library/Java/JavaVirtualMachines/sdkman-current/Contents/
  2. Symlinks Contents/Home to SDKMAN's current JDK (~/.sdkman/candidates/java/current)
  3. Generates Info.plist with proper metadata (version set to 9999 to ensure it's always selected)
  4. Automatically tracks SDKMAN's current JDK - when you switch versions with sdk use java <version>, the symlink follows

Features

  • Automatic vendor detection - Recognizes Amazon Corretto, Eclipse Temurin, Azul Zulu, GraalVM, and more
  • Dynamic JDK version extraction - Reads actual version from SDKMAN
  • Multiple commands - Install, uninstall, verify, and help
  • Safe operations - Prompts before overwriting, validates prerequisites
  • Comprehensive error handling - Proper cleanup and detailed error messages
  • ShellCheck compliant - Follows bash best practices
  • Idempotent - Safe to run multiple times
  • Professional output - Colored messages with clear status indicators

Requirements

  • macOS (Darwin)
  • SDKMAN installed with at least one JDK
  • sudo privileges (required to write to /Library/Java/JavaVirtualMachines)

Installation

# Download the script
curl -o setup_sdkman_java_home.sh https://gist.githubusercontent.com/abd3lraouf/YOUR_GIST_ID/raw/setup_sdkman_java_home.sh

# Make it executable
chmod +x setup_sdkman_java_home.sh

# Run the installation
./setup_sdkman_java_home.sh install

Usage

# Install the integration (requires sudo)
./setup_sdkman_java_home.sh install

# Verify it's working
./setup_sdkman_java_home.sh verify

# Check available Java VMs
/usr/libexec/java_home -V

# Get the current Java home path
/usr/libexec/java_home

# Remove the integration if needed
./setup_sdkman_java_home.sh uninstall

# Display help
./setup_sdkman_java_home.sh help

Example Output

$ ./setup_sdkman_java_home.sh install

=== Installing SDKMAN JDK Integration ===

✓ SDKMAN found at /Users/username/.sdkman
ℹ SDKMAN Current JDK: /Users/username/.sdkman/candidates/java/current
ℹ Version: 21.0.9
ℹ Vendor: Amazon Corretto
ℹ Creating directory structure...
✓ Created /Library/Java/JavaVirtualMachines/sdkman-current/Contents
✓ Created symlink: /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home -> ~/.sdkman/candidates/java/current
✓ Created Info.plist
✓ Permissions set

=== Installation Complete ===
✓ SDKMAN JDK integration installed successfully

$ /usr/libexec/java_home -V
Matching Java Virtual Machines (1):
    9999 (arm64) "Amazon Corretto" - "SDKMAN Current JDK" /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home

Commands

Command Description
install Set up the SDKMAN JDK integration (default command)
uninstall Remove the integration and clean up all created files
verify Verify the setup is working correctly
help Display comprehensive help message

Files Created

/Library/Java/JavaVirtualMachines/sdkman-current/
├── Contents/
│   ├── Home -> ~/.sdkman/candidates/java/current (symlink)
│   └── Info.plist

Troubleshooting

SDKMAN not found

# Install SDKMAN
curl -s "https://get.sdkman.io" | bash
source ~/.sdkman/bin/sdkman-init.sh

No current JDK set

# Install a JDK
sdk install java 21.0.9-amzn

# Or set an existing one as current
sdk use java 21.0.9-amzn

Verification fails

# Run the verify command for detailed diagnostics
./setup_sdkman_java_home.sh verify

Switching JDK versions

When you switch JDK versions with SDKMAN:

sdk use java 17.0.9-tem

The symlink automatically points to the new version - no need to re-run the script!

Technical Details

Why Version 9999?

The JVMPlatformVersion is set to 9999 in Info.plist to ensure this JDK is always selected as the default by /usr/libexec/java_home. This is important because:

  • Other manually installed JDKs might exist on the system
  • /usr/libexec/java_home selects the highest version by default
  • This ensures SDKMAN's current JDK is always preferred

Supported SDKMAN Vendors

The script auto-detects these vendors from the JDK path:

  • amzn → Amazon Corretto
  • tem → Eclipse Temurin
  • zulu → Azul Zulu
  • graal* → GraalVM
  • liberica → BellSoft Liberica
  • sapmchn → SAP Machine
  • Others → SDKMAN (vendor-code)

Use Cases

  • Xcode Projects: Build phases requiring Java (e.g., Kotlin/Native builds)
  • Shell Scripts: Scripts that use JAVA_HOME=$(/usr/libexec/java_home)
  • Development Tools: IDEs and tools expecting standard macOS Java detection
  • CI/CD Pipelines: macOS build environments using SDKMAN

Contributing

Found a bug or have a suggestion? Feel free to open an issue or submit improvements!

License

MIT License - feel free to use, modify, and distribute.

Author

Abdelraouf Sabri

References


If this script helped you, consider giving it a ⭐ star!

#!/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 -euo pipefail
# Constants
readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
readonly SCRIPT_VERSION="1.0.0"
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
cleanup() {
local exit_code=$?
if [ $exit_code -ne 0 ]; then
error "Script failed with exit code $exit_code"
fi
}
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
# 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")"
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() {
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"
read -rp "Remove and reinstall? (y/N): " response
if [[ "$response" =~ ^[Yy]$ ]]; then
uninstall_setup
else
info "Installation cancelled"
exit 0
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
${BOLD}${SCRIPT_NAME}${NC} - Version ${SCRIPT_VERSION}
${BOLD}DESCRIPTION${NC}
Configure macOS to recognize SDKMAN-installed JDKs through /usr/libexec/java_home
by creating a proper directory structure in /Library/Java/JavaVirtualMachines.
${BOLD}USAGE${NC}
${SCRIPT_NAME} [COMMAND]
${BOLD}COMMANDS${NC}
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
${BOLD}EXAMPLES${NC}
# Install the integration
${SCRIPT_NAME} install
# Verify it's working
${SCRIPT_NAME} verify
# Remove the integration
${SCRIPT_NAME} uninstall
${BOLD}HOW IT WORKS${NC}
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
${BOLD}REQUIREMENTS${NC}
- macOS
- SDKMAN installed with at least one JDK
- sudo privileges
${BOLD}FILES CREATED${NC}
${TARGET_DIR}/
${CONTENTS_DIR}/
${HOME_LINK} (symlink)
${PLIST_FILE}
${BOLD}MORE INFO${NC}
- SDKMAN: https://sdkman.io/
- Report issues: https://github.com/anthropics/claude-code/issues
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 "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment