You installed Java 17, but the terminal still runs Java 21
You install a Spring Boot project that requires Java 17. You run the package manager, verify the version, and move on. Two weeks later, a new tool demands Java 21. You install it, run java -version, and the terminal stubbornly reports 17. Your build script fails. You are not alone. Fedora ships with a robust package manager, but it does not automatically guess which Java runtime your current terminal session should use. The system keeps both versions installed, but only one gets the spotlight.
What is actually happening under the hood
Linux resolves executable names through a chain of symbolic links. When you type java, the shell looks in /usr/bin/java. That file is not a real binary. It points to /etc/alternatives/java. That second link points to the actual executable inside /usr/lib/jvm/. The alternatives system manages that middle link. It acts as a controlled switchboard. Fedora uses it for commands that have multiple interchangeable implementations, like java, editor, or pager.
When you install a new OpenJDK package, dnf registers it with the alternatives system. It does not overwrite your current default. The package manager assumes you know which version your existing services depend on. Switching the default requires an explicit command. The underlying mechanism is simple, but the path structure trips up users who expect the package manager to handle runtime selection automatically.
The alternatives system tracks two modes. Auto mode picks the highest priority number. Manual mode locks your selection until you change it again. Fedora defaults to auto mode for new installations. That is why installing a newer JDK sometimes flips the default without warning. You can force manual mode to prevent future package updates from hijacking your configuration.
Config files in /etc/ are user-modified. Files in /usr/lib/ ship with the package. Edit /etc/. Never edit /usr/lib/. The alternatives system lives in /etc/alternatives/, which means your choices survive package updates.
Install multiple versions and take control
Install the versions you need. Fedora provides OpenJDK builds for the supported releases. You can pull them down in a single transaction. Use the full JDK packages if you need to compile code. Use the headless packages if you only need to run applications and want to save disk space.
sudo dnf install java-17-openjdk java-21-openjdk # Install both runtimes in one transaction
sudo dnf install java-17-openjdk-devel java-21-openjdk-devel # Include compiler and development tools
sudo dnf clean expire-cache # Clear stale metadata before switching defaults
Verify that both versions are registered with the system. The alternatives command lists every known implementation and its priority score. Higher priority numbers win by default.
sudo alternatives --display java # Show the full chain and priority scores
You will see output listing each installed JDK path alongside a numeric priority. The system automatically selects the highest priority unless you intervene. To take manual control, run the configuration prompt.
sudo alternatives --config java # Enter interactive selection mode
The terminal prints a numbered list. Each number corresponds to a specific JDK path. Type the number for your target version and press Enter. The system updates the /etc/alternatives/java symlink immediately. No reboot is required. The change applies to all new terminal sessions and system services that rely on the default java command.
If you also need the compiler, repeat the process for javac. The alternatives system tracks java and javac as separate groups. Installing the -devel packages registers the compiler automatically.
sudo alternatives --config javac # Switch the compiler independently if needed
Reboot before you debug. Half the time the symptom is gone.
Verify the switch took effect
Open a fresh terminal window. Environment variables from your old session still point to the previous version. Run the version check.
java -version # Confirm the runtime matches your selection
javac -version # Confirm the compiler matches your selection
readlink -f /usr/bin/java # Trace the symlink chain to the actual binary
The output should print the exact version string you selected. If it still shows the old version, your shell is caching the old path or an IDE is overriding the system default. Close the terminal and try again. Do not trust a stale session.
You can also force the system into manual mode to prevent future dnf operations from flipping the switch. This is useful when you run long-lived services that must not change runtimes mid-cycle.
sudo alternatives --set java /usr/lib/jvm/java-21-openjdk/bin/java # Lock the default to version 21
sudo alternatives --set javac /usr/lib/jvm/java-21-openjdk/bin/javac # Lock the compiler to version 21
Run journalctl -xe first. Read the actual error before guessing.
Common pitfalls and what the error looks like
The alternatives system only controls the /usr/bin/java symlink. It does not touch environment variables. Many Java applications read JAVA_HOME to locate libraries and tools. If you switch the default but leave JAVA_HOME pointing to the old JDK, your build will fail with a cryptic classpath error.
Update your shell profile to point to the active JDK. Fedora stores OpenJDK installations in a predictable directory structure.
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk # Match the version you selected
export PATH=$JAVA_HOME/bin:$PATH # Ensure the bin directory is first
source ~/.bashrc # Apply changes to the current session
Add those lines to ~/.bashrc or ~/.zshrc. Source the file to apply it to your current session. If you use a desktop environment, you may also need to set JAVA_HOME in your display manager configuration or in ~/.profile to ensure GUI applications inherit it.
Another frequent issue involves IDEs and build tools. Maven, Gradle, and IntelliJ maintain their own Java home settings. They ignore the system alternatives configuration. Check your IDE preferences and your project configuration files. The error usually looks like this:
Unsupported class file major version 65
Incompatible magic value 0 in jar file
Error: Could not find or load main class com.example.Main
The build tool is using a different JDK than you think it is. Maven reads ~/.m2/settings.xml or the MAVEN_OPTS environment variable. Gradle reads gradle.properties or the JAVA_HOME variable. IntelliJ reads the Project Structure settings. Align them with your system default or explicitly point them to the correct path.
You might also encounter package conflicts if you mix Fedora packages with third-party repositories. The official Fedora repositories provide java-*-openjdk. Third-party repos sometimes ship java-*-openjdk-headless or vendor-specific builds. The alternatives system handles them fine, but dnf will refuse to install two packages that claim the same file paths. Stick to the official Fedora packages unless you have a specific vendor requirement.
If you accidentally break the symlink chain, the terminal will print a clear failure message:
java: symbol lookup error: /usr/lib/jvm/java-21-openjdk/lib/libjava.so: undefined symbol
This usually means you mixed a JDK 21 runtime with a JDK 17 library path. Reset the alternatives to auto mode and let Fedora pick the highest priority, or manually point both java and javac to the same version directory.
Trust the package manager. Manual file edits drift, snapshots stay.
When to use this versus other approaches
Use the alternatives system when you need a single system-wide default that applies to CLI tools, systemd services, and cron jobs. Use SDKMAN! when you are a developer who switches Java versions per project and wants isolated installations outside the package manager. Use Docker or Podman containers when you need to run multiple Java applications with different runtime requirements on the same host without touching system paths. Use manual symlinks in ~/.local/bin when you only need a personal override that does not affect other users or system services.
Snapshot the system before the upgrade. Future-you will thank you.