When two or more installed programs provide the same command name, the system needs a way to decide which one runs by default.
The update-alternatives command on Ubuntu, Debian, and their derivatives manages this through symbolic links in /etc/alternatives. This guide covers how to inspect alternative groups, switch between programs interactively or from a script, register custom paths, and return a group to automatic mode.
update-alternatives maintains link groups. Each group has a generic command name and one or more registered program paths. The java group, for example, controls which installed JVM /usr/bin/java starts.
The link structure is indirect by design. /usr/bin/java points to /etc/alternatives/java, which points to the active binary. Changing the alternative updates only the link in /etc/alternatives — any script or tool calling /usr/bin/java picks up the new selection without modification. Administrative state is stored in /var/lib/dpkg/alternatives.
Each group runs in one of two modes. Automatic mode selects the registered path with the highest priority. Package installs can update the active choice when they register a higher-priority entry. Manual mode preserves the administrator’s selection until it is explicitly changed or the path is removed. Package installs can add new choices but will not override the current selection.
List every registered group with its mode and active path:
bashupdate-alternatives --get-selections
List registered paths for one group:
bashupdate-alternatives --list java
See full details with priorities and slave links:
bashupdate-alternatives --display java
Slave links are related files that follow the same group selection. For java, the man page (java.1.gz) automatically switches to match the active JVM version. Groups are independent — switching java does not switch javac.
Switch interactively with --config. The command shows a numbered menu of all registered paths:
bashsudo update-alternatives --config java
Type a selection number and press Enter. Choosing 0 restores automatic mode. Any numbered choice moves the group to manual mode.
Switch without a menu using --set. Copy the exact path from --list and pass it directly. This is the right approach for provisioning scripts because it requires no user input:
bashsudo update-alternatives --set java /usr/lib/jvm/java-17-openjdk-amd64/bin/java
Register a custom alternative with --install for programs installed outside the package manager. The four arguments are: the symlink path, the group name, the real binary path, and the priority number:
bashsudo update-alternatives --install /usr/local/bin/acme-tool acme-tool /opt/acme-tool-1.0/bin/acme-tool 100sudo update-alternatives --install /usr/local/bin/acme-tool acme-tool /opt/acme-tool-2.0/bin/acme-tool 200
In auto mode, version 2.0 wins because 200 > 100. Both paths must exist on disk before registration.
Do not replace
/usr/bin/python3using update-alternatives on Ubuntu or Debian. System tools including apt depend on the distribution’s exact Python version. Use pyenv, a virtual environment, or a separate command name under/usr/local/bininstead.
Deregister a specific path with --remove. The entry is removed from the database but the binary is not deleted:
bashsudo update-alternatives --remove acme-tool /opt/acme-tool-1.0/bin/acme-tool
If the removed path was the active selection, the group returns to auto mode and selects the best remaining entry.
Restore automatic mode at any time with --auto:
bashsudo update-alternatives --auto java
Common errors and fixes:
no alternatives for NAME — the group is not registered. Check existing groups with update-alternatives --get-selections, then install a package that provides the group or register an existing binary with --installalternative path ... does not exist — the binary must exist on disk before registration. Verify with ls -l PATHA regular file blocks the alternatives link — do not add --force without checking first. Run dpkg -S /path/to/command to see which package owns the file, and back up any locally installed binary before replacing itUse --config for interactive changes, --set for scripts, and --auto to restore priority-based selection. Leave a comment below if you run into any issues.