The jdk-manager.sh script automates managing JDK versions on macOS using Homebrew and jenv. It can install or remove JDK 17, 21, and 25 (or a subset you specify), link them so /usr/libexec/java_home can detect them, and register them with jenv for version switching. It also cleans up stale jenv entries and old Homebrew versions, ensuring only the latest patch of each JDK is kept.
Created
March 28, 2026 02:55
-
-
Save rmkane/5454d0f00e943985ddb6f279f6dff8b0 to your computer and use it in GitHub Desktop.
JDK Manager
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| BREW_PREFIX="${BREW_PREFIX:-/opt/homebrew}" | |
| DEFAULT_VERSIONS=(17 21 25) | |
| usage() { | |
| cat <<EOF | |
| Usage: | |
| $(basename "$0") install [versions...] | |
| $(basename "$0") remove [versions...] | |
| Examples: | |
| $(basename "$0") install | |
| $(basename "$0") install 17 21 | |
| $(basename "$0") remove | |
| $(basename "$0") remove 25 | |
| If no versions are provided, defaults to: ${DEFAULT_VERSIONS[*]} | |
| Behavior: | |
| install | |
| - installs the requested JDKs with Homebrew | |
| - links them into /Library/Java/JavaVirtualMachines for java_home | |
| - removes stale jenv entries for those major versions | |
| - adds the current JDK homes to jenv | |
| - runs brew cleanup for the requested formulas | |
| remove | |
| - removes matching jenv entries for those major versions | |
| - removes the java_home symlinks | |
| - uninstalls the requested Homebrew formulas | |
| - runs brew cleanup for the requested formulas | |
| EOF | |
| exit 1 | |
| } | |
| log() { | |
| printf '\n==> %s\n' "$*" | |
| } | |
| die() { | |
| echo "Error: $*" >&2 | |
| exit 1 | |
| } | |
| require_cmd() { | |
| command -v "$1" >/dev/null 2>&1 || die "required command not found: $1" | |
| } | |
| brew_formula() { | |
| case "$1" in | |
| 17|21) echo "openjdk@$1" ;; | |
| 25) echo "openjdk" ;; | |
| *) die "unsupported JDK version: $1 (supported: 17, 21, 25)" ;; | |
| esac | |
| } | |
| jdk_opt_path() { | |
| echo "$BREW_PREFIX/opt/$(brew_formula "$1")" | |
| } | |
| jdk_bundle() { | |
| echo "$(jdk_opt_path "$1")/libexec/openjdk.jdk" | |
| } | |
| jdk_home() { | |
| echo "$(jdk_bundle "$1")/Contents/Home" | |
| } | |
| jdk_link() { | |
| echo "/Library/Java/JavaVirtualMachines/openjdk-$1.jdk" | |
| } | |
| ensure_jenv() { | |
| if ! command -v jenv >/dev/null 2>&1; then | |
| log "Installing jenv" | |
| brew install jenv | |
| fi | |
| export PATH="$HOME/.jenv/bin:$PATH" | |
| command -v jenv >/dev/null 2>&1 || die "jenv is not available on PATH" | |
| # Do not run: eval "$(jenv init -)" | |
| # The script only needs the jenv executable, not the interactive shell hooks. | |
| } | |
| set_versions() { | |
| if [[ $# -eq 0 ]]; then | |
| log "No JDK versions provided. Defaulting to: ${DEFAULT_VERSIONS[*]}" | |
| VERSIONS=("${DEFAULT_VERSIONS[@]}") | |
| else | |
| VERSIONS=("$@") | |
| fi | |
| } | |
| validate_versions() { | |
| local v | |
| for v in "$@"; do | |
| case "$v" in | |
| 17|21|25) ;; | |
| *) die "unsupported JDK version: $v (supported: 17, 21, 25)" ;; | |
| esac | |
| done | |
| } | |
| cleanup_jenv_version() { | |
| local version="$1" | |
| local entries=() | |
| while IFS= read -r entry; do | |
| [[ -n "$entry" ]] && entries+=("$entry") | |
| done < <(jenv versions --bare 2>/dev/null | grep -E "^(openjdk64-${version}\.|${version}(\.|$))" || true) | |
| [[ ${#entries[@]} -eq 0 ]] && return 0 | |
| log "Removing stale jenv entries for JDK $version" | |
| local entry | |
| for entry in "${entries[@]}"; do | |
| jenv remove -f "$entry" >/dev/null 2>&1 || jenv remove "$entry" >/dev/null 2>&1 || true | |
| done | |
| } | |
| brew_cleanup_versions() { | |
| local formulas=() | |
| local v | |
| for v in "$@"; do | |
| formulas+=("$(brew_formula "$v")") | |
| done | |
| log "Running brew cleanup" | |
| brew cleanup "${formulas[@]}" || true | |
| } | |
| install_versions() { | |
| local versions=("$@") | |
| local v formula bundle home link | |
| ensure_jenv | |
| for v in "${versions[@]}"; do | |
| formula="$(brew_formula "$v")" | |
| log "Installing $formula" | |
| brew install "$formula" | |
| bundle="$(jdk_bundle "$v")" | |
| home="$(jdk_home "$v")" | |
| link="$(jdk_link "$v")" | |
| [[ -d "$bundle" ]] || die "missing JDK bundle: $bundle" | |
| [[ -d "$home" ]] || die "missing JDK home: $home" | |
| log "Linking JDK $v for java_home" | |
| sudo mkdir -p /Library/Java/JavaVirtualMachines | |
| sudo ln -sfn "$bundle" "$link" | |
| cleanup_jenv_version "$v" | |
| log "Adding JDK $v to jenv" | |
| jenv add "$home" | |
| done | |
| log "Enabling jenv export plugin" | |
| jenv enable-plugin export || true | |
| jenv rehash | |
| brew_cleanup_versions "${versions[@]}" | |
| log "Done. Verify:" | |
| /usr/libexec/java_home -V || true | |
| jenv versions | |
| } | |
| remove_versions() { | |
| local versions=("$@") | |
| local v formula link | |
| ensure_jenv | |
| for v in "${versions[@]}"; do | |
| formula="$(brew_formula "$v")" | |
| link="$(jdk_link "$v")" | |
| cleanup_jenv_version "$v" | |
| log "Removing java_home link for JDK $v" | |
| sudo rm -f "$link" | |
| if brew list --versions "$formula" >/dev/null 2>&1; then | |
| log "Uninstalling $formula" | |
| brew uninstall "$formula" || true | |
| else | |
| log "$formula is not installed" | |
| fi | |
| done | |
| jenv rehash | |
| brew_cleanup_versions "${versions[@]}" | |
| log "Done. Remaining:" | |
| /usr/libexec/java_home -V || true | |
| jenv versions | |
| } | |
| main() { | |
| require_cmd brew | |
| require_cmd sudo | |
| [[ $# -eq 0 ]] && usage | |
| local cmd="$1" | |
| shift | |
| VERSIONS=() | |
| set_versions "$@" | |
| validate_versions "${VERSIONS[@]}" | |
| case "$cmd" in | |
| install) install_versions "${VERSIONS[@]}" ;; | |
| remove) remove_versions "${VERSIONS[@]}" ;; | |
| *) usage ;; | |
| esac | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment