How to Install SDKMAN on Fedora 44

Install SDKMAN on Fedora 44 to give each developer an independent Java and build-tool environment without replacing the system's RPM packages. Follow the upstream setup, switch JDKs and project tools, pin versions with .sdkmanrc, and keep interactive shells or CI jobs predictable.

PublishedAuthorJoshua JamesRead time13 minGuide typeFedora

Fedora’s packaged Java tools work well when one system-wide version is enough, but they become awkward when projects require different JDK, Maven, Gradle, or Kotlin releases. Installing SDKMAN on Fedora adds a per-user version manager that can switch those toolchains without replacing Fedora’s RPM-managed Java packages.

SDKMAN’s upstream installer writes to ~/.sdkman and loads the sdk shell function from Bash or Zsh startup files. Use it for interactive development shells and project-specific runtimes; keep Fedora’s OpenJDK packages for system services, other users, or software that must run outside your login environment.

Install SDKMAN on Fedora

SDKMAN publishes one supported Unix installation path through its official installer. Fedora’s DNF package manager supplies the prerequisite commands, while SDKMAN maintains its own files and installed candidates under your home directory.

The DNF commands target mutable Fedora Workstation and Server systems. Fedora Atomic desktops use Flatpak, Toolbx, and package layering instead of ordinary host DNF transactions. If you choose to install SDKMAN inside a Toolbx or another development container, run both the prerequisites and SDKMAN in that same environment. Do not treat this DNF path as an Atomic host installation.

Prepare Fedora for the SDKMAN Installer

Review Fedora’s available system updates separately from the SDKMAN prerequisites. The following transaction can update unrelated system packages, so inspect the proposed changes before approving them and postpone it when the wider update is not appropriate:

sudo dnf upgrade --refresh

Whether you apply or postpone the wider update, install SDKMAN’s curl, zip, and unzip requirements plus the commands used later for diagnostics, profile cleanup, and backups:

sudo dnf install bash curl zip unzip tar findutils sed ca-certificates gzip grep

These commands use sudo only for Fedora package management. If your account is not an administrator, ask one to install the prerequisites; do not grant yourself access. Administrators can review the separate guide for managing sudo access on Fedora. Do not use sudo for the SDKMAN installer or normal sdk commands.

Most Fedora Workstation installations already contain several of these utilities, while Server and minimal images may not. DNF resolves the installed packages and adds or updates anything required by the transaction. DNF handles Fedora system updates and SDKMAN’s prerequisites, but it does not install or manage SDKMAN itself. The curl command guide covers download diagnostics used later.

Install SDKMAN with the Upstream Installer

Review What the SDKMAN Installer Changes

The upstream script creates ~/.sdkman, downloads SDKMAN’s script and native components, and adds an initialisation line to the applicable shell startup file. For a Bash login, it adds one guarded source line to ~/.bashrc and tells you how to load it. SDKMAN remains user-scoped and does not create an RPM package or system service.

Run the installer as your normal user. Running it with sudo can create a separate installation under /root/.sdkman or leave files in your home directory owned by root.

Download and Run the SDKMAN Installer

SDKMAN’s official install method retrieves a moving script from get.sdkman.io and does not provide a fixed checksum for that response. Saving the response to a temporary file avoids piping network output directly into Bash and allows bash -n to catch syntax errors, but the command still executes the content returned by the official endpoint without an independent integrity or human content review. The guarded subshell stops on failure and removes the temporary copy on exit.

(
  set -e
  sdkman_installer="$(mktemp)"
  trap 'rm -f "$sdkman_installer"' EXIT
  curl -fsSL https://get.sdkman.io -o "$sdkman_installer"
  bash -n "$sdkman_installer"
  bash "$sdkman_installer"
)

The installer should finish by asking you to open a new terminal or source the SDKMAN initialisation file. Stop if the final message differs substantially from the documented upstream flow.

Load SDKMAN in the Current Shell

Load the SDKMAN shell function without closing the terminal:

source "$HOME/.sdkman/bin/sdkman-init.sh"

A new Bash terminal reads the persistent line from ~/.bashrc. SDKMAN also supports Zsh, but check the installer output for the startup file it selected instead of assuming that every shell profile was edited. Fedora users changing shells can follow the separate steps to install Zsh on Fedora.

Verify SDKMAN on Fedora

In Bash, confirm that sdk is loaded as a shell function:

type -t sdk
function

Then ask SDKMAN for its installed script and native component versions. Zsh users can use this version command without the Bash-specific type -t check:

sdk version

The version numbers change as upstream releases updates. In a fresh Bash terminal, repeat both commands; in Zsh, repeat sdk version. Success there proves the startup-file change is persistent rather than active only in the current session.

Install SDKMAN Without Editing Shell Profiles

CI jobs, disposable build environments, and other non-interactive sessions can use SDKMAN’s CI mode without modifying ~/.bashrc or ~/.zshrc. Use this path instead of the normal interactive installer on a clean account.

(
  set -e
  sdkman_installer="$(mktemp)"
  trap 'rm -f "$sdkman_installer"' EXIT
  curl -fsSL 'https://get.sdkman.io?ci=true&rcupdate=false' -o "$sdkman_installer"
  bash -n "$sdkman_installer"
  bash "$sdkman_installer"
)

CI mode answers installer prompts, disables colour output, and turns off SDKMAN’s self-update feature. Load the manager explicitly in each job step that needs it:

export SDKMAN_DIR="$HOME/.sdkman"
source "$SDKMAN_DIR/bin/sdkman-init.sh"
sdk version

Some CI scripts enable Bash nounset with set -u. SDKMAN’s initialisation and shell functions can reference unset internal variables, causing an unbound variable error. In that case, run the entire SDKMAN-dependent part of the job step in a subshell with nounset disabled:

(
  set +u
  export SDKMAN_DIR="$HOME/.sdkman"
  source "$SDKMAN_DIR/bin/sdkman-init.sh"
  sdk version
)

Place any additional sdk commands and candidate-dependent build commands before the closing parenthesis. When the subshell exits, the caller keeps its original nounset and PATH state. Jobs that do not enable nounset should use the simpler block above.

Use SDKMAN on Fedora

The sdk command is a shell function, so run it as the same unprivileged user who owns ~/.sdkman. Each candidate installed through SDKMAN stays separate from Fedora’s RPM database and follows SDKMAN’s update and removal commands.

List SDKMAN Candidates and Versions

List every supported candidate, then narrow the view to Java versions:

sdk list
sdk list java

SDKMAN may open long lists in a pager. Press q to exit, use / to search, and copy the full version identifier when you need a specific vendor or JDK release. The Java list is generated for the current platform, so copy identifiers from the same Fedora machine instead of assuming that an identifier from another architecture is available.

Install Java with SDKMAN

Install the current stable Java candidate. SDKMAN normally asks whether the new version should become the default for future shells; pressing Enter accepts the default answer.

sdk install java

Verify the SDKMAN selection, runtime, compiler, and active command paths:

sdk current java
java -version
javac -version
command -v java
command -v javac

When the SDKMAN JDK is active, the command paths should resolve beneath ~/.sdkman/candidates/java/current/bin. Use the checks for finding the active Java version in Linux when runtime, compiler, and environment results disagree.

Compile and run a disposable program to prove that both the compiler and runtime work from the selected JDK. The trap removes the temporary directory even if compilation fails:

(
  set -e
  java_test_dir="$(mktemp -d)"
  trap 'rm -rf "$java_test_dir"' EXIT
  cat > "$java_test_dir/Hello.java" <<'EOF'
public class Hello {
    public static void main(String[] args) {
        System.out.println("SDKMAN Java works on Fedora");
    }
}
EOF
  cd "$java_test_dir"
  javac Hello.java
  java Hello
)
SDKMAN Java works on Fedora

SDKMAN’s Java installation belongs to one user and an initialised shell. Use Fedora’s package-managed path to install OpenJDK on Fedora when Java must be available to system services, root, or multiple users.

Install and Switch Specific Java Versions

Copy an exact identifier from sdk list java on this Fedora machine, then edit the first line before running the block. The use command changes only the current shell:

java_version='replace-with-version-identifier'
sdk install java "$java_version"
sdk use java "$java_version"
sdk current java

Set the selected version as the default for new shells when the project requires it beyond the current terminal:

java_version='replace-with-version-identifier'
sdk default java "$java_version"

Opening a new terminal after sdk use returns to the configured default. Opening a new terminal after sdk default keeps the selected version active; confirm it there with sdk current java.

Install Maven, Gradle, or Kotlin with SDKMAN

Install only the build tools required by your projects. Each candidate remains under ~/.sdkman/candidates and can be switched or removed independently.

Each block installs one candidate, confirms SDKMAN selected it, verifies its command path, and runs its native version check.

For Maven, run:

sdk install maven
sdk current maven
command -v mvn
mvn -version

For Gradle, run:

sdk install gradle
sdk current gradle
command -v gradle
gradle -version

For Kotlin, run:

sdk install kotlin
sdk current kotlin
command -v kotlin
kotlin -version

The active path should resolve beneath the matching ~/.sdkman/candidates/<name>/current/bin directory. The mvn -version result also identifies the Java runtime Maven uses, which helps when Fedora’s system Java and an SDKMAN JDK coexist. Teams standardising on Fedora RPMs can instead install Apache Maven on Fedora with DNF.

Pin Project Versions with .sdkmanrc

An .sdkmanrc file keeps a project’s expected Java and build-tool versions beside its source. Set project_dir to an existing checkout. The guard prevents a failed directory change from creating .sdkmanrc in the wrong location:

project_dir="$HOME/path/to/project"
if cd "$project_dir"; then
  sdk env init && cat .sdkmanrc
else
  printf 'Project directory not found: %s\n' "$project_dir" >&2
fi

The file uses one candidate=identifier pair per line. Add only exact identifiers returned by sdk list <candidate> on the same Fedora machine, for example:

java=replace-with-installed-java-identifier
maven=replace-with-installed-maven-identifier

Review a repository’s .sdkmanrc before installing from it. Return to the guarded project directory, then install missing candidates and activate the listed versions in the current shell:

project_dir="$HOME/path/to/project"
if cd "$project_dir"; then
  sdk env install && sdk env
else
  printf 'Project directory not found: %s\n' "$project_dir" >&2
fi

Restore your normal default versions after leaving the project:

sdk env clear

Automatic switching is available through sdk config by setting sdkman_auto_env=true. Manual sdk env activation is easier to audit because entering a directory does not silently change the active toolchain.

Update SDKMAN and Installed SDKs on Fedora

DNF manages Fedora’s RPM packages, including the prerequisites and any system JDK. SDKMAN owns its shell code, candidate catalogue, downloaded SDKs, and version symlinks, so keep these update paths separate.

Update SDKMAN and Candidate Metadata

Update the manager itself, then refresh the catalogue of available candidates and versions:

sdk selfupdate
sdk update

CI mode disables the self-update feature to keep automated runs predictable. Rebuild the CI environment or install SDKMAN deliberately when its managed version needs to change.

Review and Apply Candidate Upgrades

Compare all installed candidates with the current catalogue. When SDKMAN finds newer prescribed defaults, it can prompt to use them; answer n if you want to review the result without changing the current defaults:

sdk upgrade

The command reports when all candidates are current or lists the installed and prescribed defaults when an update is available. To upgrade Java after reviewing that result, run the candidate-specific action:

sdk upgrade java

A candidate-specific upgrade can download a release and make it the new default. Read its output and prompts before approving changes, especially when a project is not pinned with .sdkmanrc. SDKMAN can retain the older installed version after changing the default, so inspect sdk list <candidate> and remove obsolete versions deliberately. If you want to test a new identifier without changing the default, install that exact version first and switch with sdk use. Confirm the active versions after any change:

sdk current

Use sdk flush when SDKMAN instructs you to clear stale local state. Do not manually delete ~/.sdkman/tmp, because SDKMAN manages that directory and its cache layout.

Remove an SDK or SDKMAN from Fedora

Removing one managed Java or build-tool version is different from deleting SDKMAN itself. Keep project configuration, installed candidates, shell startup lines, and the manager directory separate during cleanup.

Remove One Installed SDK Version

List installed versions and copy the exact identifiers marked as installed:

sdk list java

To remove a Java version that is neither active nor the default, set old_java to its exact installed identifier. The block checks the active JAVA_HOME and SDKMAN’s default current symlink separately before uninstalling:

old_java='replace-with-inactive-installed-version'
old_java_dir="$HOME/.sdkman/candidates/java/$old_java"
old_java_real="$(readlink -f "$old_java_dir" 2>/dev/null || true)"
active_java_dir="$(readlink -f "${JAVA_HOME:-/nonexistent}" 2>/dev/null || true)"
default_java_dir="$(readlink -f "$HOME/.sdkman/candidates/java/current" 2>/dev/null || true)"

if [ ! -d "$old_java_dir" ]; then
  printf 'Not installed: %s\n' "$old_java" >&2
elif [ "$old_java_real" = "$active_java_dir" ]; then
  printf 'Refusing to remove the active version: %s\n' "$old_java" >&2
elif [ "$old_java_real" = "$default_java_dir" ]; then
  printf 'Refusing to remove the default version: %s\n' "$old_java" >&2
else
  sdk uninstall java "$old_java" &&
  test ! -e "$old_java_dir" &&
  printf 'Removed: %s\n' "$old_java"
fi

unset old_java_dir old_java_real active_java_dir default_java_dir

When the version being removed is active, is the default for new shells, or has uncertain state, choose a different installed version first. This branch confirms that both identifiers are different and installed, then chains the switch, default change, and uninstall so a failed replacement cannot continue to removal:

remaining_java='replace-with-installed-version-to-keep'
old_java='replace-with-active-version-to-remove'
remaining_java_dir="$HOME/.sdkman/candidates/java/$remaining_java"
old_java_dir="$HOME/.sdkman/candidates/java/$old_java"

if [ "$remaining_java" = "$old_java" ]; then
  printf '%s\n' 'The replacement and removal identifiers must differ.' >&2
elif [ ! -d "$remaining_java_dir" ] || [ ! -d "$old_java_dir" ]; then
  printf '%s\n' 'Both Java identifiers must already be installed.' >&2
elif sdk use java "$remaining_java" &&
     sdk default java "$remaining_java" &&
     sdk uninstall java "$old_java"; then
  sdk current java
  test ! -e "$old_java_dir" && printf 'Removed: %s\n' "$old_java"
else
  printf '%s\n' 'Replacement failed; the uninstall step did not complete.' >&2
fi

unset remaining_java_dir old_java_dir

Adapt the general replacement branch for candidates such as maven, gradle, or kotlin; the inactive-version branch above is specific to Java because it checks JAVA_HOME. Removing an SDKMAN candidate does not remove a Fedora RPM with the same command name.

Back Up and Remove the Entire SDKMAN Installation

Deleting ~/.sdkman removes SDKMAN, every candidate installed through it, downloaded archives, configuration, and version state for the current user. Create a backup first when any installed toolchain or configuration may be needed later.

SDK archives can make ~/.sdkman large. Check its size and the free space on the destination filesystem before creating a second copy:

du -sh "$HOME/.sdkman"
df -h "$HOME"

When the destination has enough space, create a collision-resistant archive and verify that it can be listed. The block refuses to overwrite an existing file:

(
  set -e
  umask 077
  sdkman_backup="$HOME/sdkman-backup-$(date +%F-%H%M%S).tar.gz"
  if [ -e "$sdkman_backup" ]; then
    printf 'Refusing to overwrite: %s\n' "$sdkman_backup" >&2
    exit 1
  fi
  backup_complete=false
  trap '[ "$backup_complete" = true ] || rm -f -- "$sdkman_backup"' EXIT
  tar -czf "$sdkman_backup" -C "$HOME" .sdkman
  tar -tzf "$sdkman_backup" >/dev/null
  backup_complete=true
  trap - EXIT
  ls -lh "$sdkman_backup"
)

Read the displayed filename and size before continuing. The archive stays outside ~/.sdkman, so the removal command does not delete it.

Continue only after confirming the backup is readable or deciding that no backup is needed. The next command permanently removes every SDKMAN-managed candidate and configuration file for the current user.

rm -rf -- "${HOME:?HOME is not set}/.sdkman"

Remove SDKMAN Shell Initialisation

Find the exact current and legacy upstream lines that the automated cleanup recognises. The same pattern is reused for deletion, so the loop cannot remove a line that this preview omitted:

zshrc_path="${ZDOTDIR:-$HOME}/.zshrc"
sdkman_profile_pattern='^[[:space:]]*#THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK!!![[:space:]]*$|^[[:space:]]*export SDKMAN_DIR="\$HOME/\.sdkman"[[:space:]]*$|^[[:space:]]*\[\[ -s "\$HOME/\.sdkman/bin/sdkman-init\.sh" \]\] && source "\$HOME/\.sdkman/bin/sdkman-init\.sh"[[:space:]]*$|^[[:space:]]*\[\[ -s "\$SDKMAN_DIR/bin/sdkman-init\.sh" \]\] && source "\$SDKMAN_DIR/bin/sdkman-init\.sh"[[:space:]]*$'
grep -nE "$sdkman_profile_pattern" "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile" "$zshrc_path" 2>/dev/null || true

No output means the automated pattern found nothing. If you wrote a custom SDKMAN source line, search the specific profile for sdkman-init.sh, back up that file, and edit the reviewed line manually. After reviewing the exact-match output, back up each regular profile to a unique file and remove only those printed patterns:

(
  set -e
  zshrc_path="${ZDOTDIR:-$HOME}/.zshrc"
  sdkman_profile_pattern='^[[:space:]]*#THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK!!![[:space:]]*$|^[[:space:]]*export SDKMAN_DIR="\$HOME/\.sdkman"[[:space:]]*$|^[[:space:]]*\[\[ -s "\$HOME/\.sdkman/bin/sdkman-init\.sh" \]\] && source "\$HOME/\.sdkman/bin/sdkman-init\.sh"[[:space:]]*$|^[[:space:]]*\[\[ -s "\$SDKMAN_DIR/bin/sdkman-init\.sh" \]\] && source "\$SDKMAN_DIR/bin/sdkman-init\.sh"[[:space:]]*$'
  for profile in "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile" "$zshrc_path"; do
    [ -f "$profile" ] || continue
    if [ -L "$profile" ]; then
      printf 'Skipped symlink: %s -> %s\n' "$profile" "$(readlink -f -- "$profile")"
      continue
    fi
    grep -Eq "$sdkman_profile_pattern" "$profile" || continue
    profile_backup="$(mktemp "${profile}.sdkman-backup.XXXXXX")"
    cp -a -- "$profile" "$profile_backup"
    sed -E -i "\%${sdkman_profile_pattern}%d" "$profile"
    printf 'Updated %s; backup: %s\n' "$profile" "$profile_backup"
  done
)

If the loop reports a symlink, inspect the resolved target, create a separate content backup, and remove the reviewed SDKMAN source line from that target manually. Skipping it prevents sed -i from replacing a managed dotfile symlink with a regular file.

Unload the function from the current Bash session and verify that the manager directory is absent. This prevents further sdk calls, but the old shell can still contain stale candidate paths or variables such as JAVA_HOME. Do not reuse it for builds after removal:

unset -f sdk 2>/dev/null || true
unset SDKMAN_DIR
hash -r
command -v sdk 2>/dev/null || echo "SDKMAN is no longer loaded"
test ! -d "$HOME/.sdkman" && echo "SDKMAN directory removed"

Close that terminal and open a fresh one for the authoritative final check:

command -v sdk 2>/dev/null || echo "SDKMAN is absent in the fresh shell"
test ! -d "$HOME/.sdkman" && echo "SDKMAN directory removed"
zshrc_path="${ZDOTDIR:-$HOME}/.zshrc"
grep -nE 'SDKMAN_DIR|sdkman-init\.sh|THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK' "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile" "$zshrc_path" 2>/dev/null || echo "No SDKMAN profile entries found"
command -v java 2>/dev/null || echo "No other Java command is installed"

Success means sdk is absent, ~/.sdkman is gone, and no SDKMAN profile entries print. Java may resolve to Fedora’s /usr/bin/java when a package-managed JDK remains. Project-owned .sdkmanrc files stay in their repositories, and the shared Fedora prerequisite packages remain installed. Review the uniquely named *.sdkman-backup.* profile copies before deleting them.

Restore an SDKMAN Backup on Fedora

Restore only an archive you created and trust for the same user and CPU architecture. SDKMAN candidates can contain platform-specific binaries, so do not treat this backup as a portable cross-architecture installation. Close other shells using SDKMAN and edit sdkman_backup to the exact archive path.

The guarded block verifies the archive, refuses an existing ~/.sdkman path, rejects entries outside the expected directory, and removes a newly created partial tree if extraction or verification fails:

(
  set -e
  sdkman_backup="$HOME/sdkman-backup-replace-with-timestamp.tar.gz"
  archive_list="$(mktemp)"
  restore_started=false
  restore_complete=false
  cleanup_restore() {
    rm -f "$archive_list"
    if [ "$restore_started" = true ] && [ "$restore_complete" != true ]; then
      rm -rf -- "${HOME:?HOME is not set}/.sdkman"
    fi
  }
  trap cleanup_restore EXIT

  [ -f "$sdkman_backup" ] || {
    printf 'Backup not found: %s\n' "$sdkman_backup" >&2
    exit 1
  }
  if [ -e "$HOME/.sdkman" ] || [ -L "$HOME/.sdkman" ]; then
    printf '%s\n' 'Refusing to overwrite the existing ~/.sdkman path.' >&2
    exit 1
  fi

  tar -tzf "$sdkman_backup" > "$archive_list"
  if grep -Eq '(^|/)\.\.(/|$)' "$archive_list" ||
     grep -Ev '^\.sdkman(/|$)' "$archive_list" | grep -q .; then
    printf '%s\n' 'Archive contains an unexpected path; nothing was extracted.' >&2
    exit 1
  fi

  restore_started=true
  tar --no-same-owner -xzf "$sdkman_backup" -C "$HOME"
  test -s "$HOME/.sdkman/bin/sdkman-init.sh"
  restore_complete=true
)

Load the restored manager and confirm its candidate state:

export SDKMAN_DIR="$HOME/.sdkman"
source "$SDKMAN_DIR/bin/sdkman-init.sh"
sdk version
sdk current

The SDKMAN archive does not contain your shell profiles. For Bash, restore the guarded startup line once, then open a fresh terminal and repeat sdk version:

sdkman_init='[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"'
touch "$HOME/.bashrc"
grep -qxF "$sdkman_init" "$HOME/.bashrc" || printf '\n%s\n' "$sdkman_init" >> "$HOME/.bashrc"

Zsh users should add the same guarded line to the startup file selected by the installer. If the restored manager or candidates fail on the original architecture, remove the restored tree and perform a clean upstream installation instead of forcing incompatible binaries.

Troubleshoot SDKMAN on Fedora

The Installer Finds an Existing SDKMAN Installation

The installer exits without overwriting an existing ~/.sdkman tree. Load that installation and force a manager refresh instead of running the bootstrap script with sudo:

source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk selfupdate force
sdk version

Use the full backup and removal path before reinstalling only when the existing initialisation file is missing or the SDKMAN tree is damaged.

The SDK Command Is Not Found

A missing sdk command has two different causes. First, report whether the initialisation file exists:

if [ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]; then
  printf '%s\n' 'SDKMAN initialisation file exists.'
else
  printf '%s\n' 'SDKMAN initialisation file is missing.' >&2
fi

If the file is missing, do not append a profile entry that points to it. Back up anything recoverable, remove the damaged SDKMAN tree, and perform a clean installation. When the file exists, inspect the startup files separately:

zshrc_path="${ZDOTDIR:-$HOME}/.zshrc"
grep -n 'sdkman-init\.sh' "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile" "$zshrc_path" 2>/dev/null || echo "No SDKMAN startup entry found"

CI mode intentionally leaves profile files unchanged, so export SDKMAN_DIR and source the initialisation file in every job step that uses SDKMAN. For an interactive Bash installation with an existing initialisation file but no matching startup line, append the exact guarded source line once:

if [ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]; then
  sdkman_init='[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"'
  touch "$HOME/.bashrc"
  grep -qxF "$sdkman_init" "$HOME/.bashrc" || printf '\n%s\n' "$sdkman_init" >> "$HOME/.bashrc"
  source "$HOME/.sdkman/bin/sdkman-init.sh"
else
  printf '%s\n' 'Initialisation file missing; reinstall SDKMAN first.' >&2
fi

In Bash, confirm that the repair loaded a function and a working manager:

type -t sdk
sdk version

The first command should print function. Zsh users should add the guarded source line to the startup file reported by the installer, then use the cross-shell version check:

sdk version

SDKMAN Reports an Unbound Variable

Bash startup files or scripts that enable nounset with set -u can expose unset variables inside SDKMAN’s initialisation or shell functions. For an interactive shell, disable nounset before sourcing SDKMAN and leave it disabled while running sdk commands:

set +u
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk version

If ~/.bashrc enables nounset before the SDKMAN source line, move that strict setting out of the interactive profile or scope it only to scripts that need it. In CI, keep the entire SDKMAN-dependent step inside a nounset-disabled subshell so the caller’s original shell options remain unchanged.

The Installer or Shell Reports a Missing Command

The upstream installer checks for curl, zip, and unzip. Identify which one is unavailable:

for command_name in curl zip unzip; do
  command -v "$command_name" >/dev/null || printf 'Missing: %s\n' "$command_name"
done

Any printed command maps directly to the package of the same name. Install the full prerequisite set when backup and troubleshooting commands are also needed:

sudo dnf install bash curl zip unzip tar findutils sed ca-certificates gzip grep

Repeat the command check. When it prints nothing, rerun the guarded bootstrap workflow because a missing prerequisite can stop installation before a usable initialisation file exists:

missing_prerequisite=0
for command_name in curl zip unzip; do
  if ! command -v "$command_name" >/dev/null; then
    printf 'Still missing: %s\n' "$command_name" >&2
    missing_prerequisite=1
  fi
done

if [ "$missing_prerequisite" -eq 0 ]; then
  (
    set -e
    sdkman_installer="$(mktemp)"
    trap 'rm -f "$sdkman_installer"' EXIT
    curl -fsSL https://get.sdkman.io -o "$sdkman_installer"
    bash -n "$sdkman_installer"
    bash "$sdkman_installer"
  )
else
  printf '%s\n' 'Install the remaining prerequisites before rerunning SDKMAN.' >&2
fi
unset missing_prerequisite

Source SDKMAN only after confirming that the installer created a non-empty initialisation file:

if [ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]; then
  source "$HOME/.sdkman/bin/sdkman-init.sh"
  sdk version
else
  printf '%s\n' 'SDKMAN initialisation is still missing; do not source it.' >&2
fi

If the rerun reports an existing but damaged SDKMAN tree, use the backup and full-removal workflow before attempting one clean installation.

Fedora Java or Maven Still Wins on PATH

Fedora RPMs and SDKMAN candidates can coexist, but the first matching path in $PATH controls the command that runs. Compare the active command paths, SDKMAN selections, and resolved Java binary:

command -v java
command -v javac
command -v mvn
sdk current
readlink -f "$(command -v java)"

A path under /usr/bin means Fedora’s system package is winning. The repair below uses Bash: source SDKMAN, set and activate the exact required version, clear Bash’s command cache, and test again. Zsh users should source the manager, set the required default and current version, then open a fresh Zsh terminal before repeating the cross-shell path checks.

source "$HOME/.sdkman/bin/sdkman-init.sh"
java_version='replace-with-installed-version-identifier'
sdk default java "$java_version"
sdk use java "$java_version"
hash -r
sdk current java
command -v java
readlink -f "$(command -v java)"

Use the corresponding candidate name and identifier when Maven, Gradle, or Kotlin is affected. The guide to setting the Java environment path on Fedora covers the system-managed alternative and JAVA_HOME behaviour.

An IDE launched from Fedora’s desktop may use a different environment from the terminal. Edit the version identifier, print the exact JDK directory, then select that path in the IDE’s project SDK settings:

java_version='replace-with-version-identifier'
sdk home java "$java_version"

SDKMAN Files Are Owned by Root

Permission errors after an earlier sudo install or command can leave part of the user-scoped tree owned by root. Find the first ownership mismatch before changing it:

find "$HOME/.sdkman" ! -user "$(id -un)" -print -quit

No output means no mismatched owner was found. When the command prints a path and ~/.sdkman belongs only to your account, restore ownership to the current user and primary group:

sudo chown -R "$(id -un)":"$(id -gn)" "$HOME/.sdkman"
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk version

Candidate Lists Fail or Stay Stale

Test the candidate API before changing local SDKMAN state:

curl -fsS https://api.sdkman.io/2/candidates/all >/dev/null

A failing request can indicate a local DNS, proxy, firewall, or certificate problem, but it can also be an upstream incident. Check the official SDKMAN service status before changing Fedora’s network or trust configuration. Do not enable sdkman_insecure_ssl to bypass certificate verification.

When the API request succeeds but lists remain stale, clear SDKMAN-managed caches and refresh the catalogue:

sdk flush
sdk update
sdk list java

Use sdk flush instead of manually deleting ~/.sdkman/tmp or other internal directories.

Conclusion

SDKMAN now provides per-user Java, Maven, Gradle, and Kotlin versions without replacing Fedora’s system packages. Keep shared runtimes and services on Fedora’s RPM-managed path, verify new toolchains with their native commands, and commit a reviewed .sdkmanrc when a project needs repeatable versions. Use SDKMAN for candidate maintenance and its own removal rather than mixing those tasks with DNF.

Share this guide

Help another Linux user troubleshoot faster

Share this guide with someone troubleshooting Linux systems or saving it for later.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show our tutorials more often in Top Stories and mark them as preferred in AI Mode and AI Overviews when relevant.

Add LinuxCapable as a preferred source on Google
Search LinuxCapable

Need another guide?

Search LinuxCapable for package installs, commands, troubleshooting, and follow-up guides related to what you just read.

Found this guide useful?

Support LinuxCapable to keep tutorials free and up to date.

Buy me a coffeeBuy me a coffee
Before commenting, please review our Comments Policy.
Formatting tips for your comment

You can use basic HTML to format your comment. Useful tags currently allowed in published comments:

You type Result
<code>command</code> command
<strong>bold</strong> bold
<em>italic</em> italic
<a href="https://example.com">link</a> link
<blockquote>quote</blockquote> quote block

Add to the discussion

Questions, fixes, command output, and version notes help keep this guide current.

Verify before posting: