You need the compiler, not just the runtime
You cloned a project, ran the build script, and got Error: JAVA_HOME is not defined. Or your IDE refuses to compile, claiming the javac command is missing. You installed Java, but the system only has the runtime. You need the full development kit to turn source code into bytecode, and you need to tell build tools exactly where that kit lives.
Fedora ships OpenJDK by default. There is no Oracle license negotiation and no proprietary runtime. The packages are split to keep the system lean. The runtime package lets you execute Java applications. The development package adds the compiler and tools. If you only installed the runtime, your build will fail.
How Fedora manages Java versions
Fedora uses the alternatives system to handle multiple Java installations. This system maintains a chain of symlinks that points /usr/bin/java to the active version. The chain looks like this: /usr/bin/java links to /etc/alternatives/java, which links to the real binary in /usr/lib/jvm/.
This design lets you install Java 11, 17, and 21 side by side. The alternatives database tracks which version is the default. When you switch the default, the symlinks update automatically. You never edit symlinks in /usr/bin manually. The package manager owns those files. Manual edits get overwritten on the next update.
Fedora's package naming follows a strict pattern. The version number is part of the package name. java-21-openjdk is the runtime for version 21. java-21-openjdk-devel is the development kit. The -devel suffix is the key. Without it, you get the JRE. With it, you get the JDK.
Run dnf search openjdk to see what is available. Fedora typically carries the current release and the active Long Term Support versions. Check the output before installing to confirm the version you need exists in the repository.
Install the development kit
Here's how to install the full development kit for the current Long Term Support release. The -devel package pulls in the runtime as a dependency, so you only need to request one package.
sudo dnf install java-21-openjdk-devel -y
# -devel includes javac, jar, and other tools required to compile code
# java-21-openjdk is the current LTS version in Fedora repositories
# -y automatically confirms the transaction without prompting
The package manager resolves dependencies and places the files in /usr/lib/jvm/java-21-openjdk/. The alternatives system registers the new binaries and sets them as the default if no other version is active.
Verify the installation immediately. Run the version checks to confirm both the runtime and the compiler are linked correctly.
java -version
# Confirms the runtime binary is accessible and reports the JVM version
javac -version
# Verifies the compiler is present; missing this output means you only have the JRE
Both commands should report version 21. If javac fails with command not found, the -devel package did not install. Check the dnf output for errors and retry the installation.
Configure JAVA_HOME correctly
Build tools like Maven, Gradle, and older frameworks rely on the JAVA_HOME environment variable. These tools do not always trust the PATH. They look for JAVA_HOME to find the JDK root directory, then append /bin to locate the compiler.
Finding the correct path requires resolving the symlink chain. The which command returns /usr/bin/java, which is not the installation directory. You need the real path where the JDK lives.
Here's how to extract the JDK root path from the symlink chain. This command resolves the full path and strips the binary suffix.
readlink -f $(which java) | sed 's|/bin/java||'
# Resolves the symlink chain to find the actual binary location
# Strips the /bin/java suffix to leave only the JDK root directory
# Output should look like /usr/lib/jvm/java-21-openjdk-21.0.x.x.x.fc41.x86_64
Copy the output and add it to your shell profile. Edit ~/.bashrc for Bash or ~/.zshrc for Zsh. This sets the variable for your user session.
echo 'export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-21.0.x.x.x.fc41.x86_64' >> ~/.bashrc
# Appends the export command to your shell profile so it persists across logins
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc
# Prepends the JDK bin directory to PATH to ensure the correct tools are found first
Reload your shell to apply the changes. Do not restart the terminal if you can avoid it. Sourcing the file is faster and keeps your current session intact.
source ~/.bashrc
# Reloads the profile to apply JAVA_HOME and PATH changes to the current shell
Confirm the variable is set. Run echo $JAVA_HOME to see the path. If it prints the directory, the configuration is active.
Switch versions with alternatives
You might install Java 17 for a legacy project while keeping Java 21 for new work. The alternatives command lets you switch the system default without removing packages.
Here's how to change the default Java runtime. This command opens an interactive menu of installed versions.
sudo alternatives --config java
# Opens the interactive menu to select the system default java binary
# Type the number corresponding to the desired version and press Enter
# Updates the symlink at /usr/bin/java to point to the selected version
The output lists installed versions with a number prefix. The current default has an asterisk. Enter the number for the version you want. The system updates the symlink immediately.
You must repeat this process for javac. Switching java does not automatically switch the compiler. If java points to version 21 and javac points to version 17, your builds will fail with version mismatch errors.
sudo alternatives --config javac
# Synchronizes the compiler symlink with the runtime choice
# Prevents errors where java and javac report different versions
Run java -version and javac -version again. Both must report the same version number. A mismatch means the alternatives configuration is out of sync.
Verify the full pipeline
A version check only proves the binaries exist. You need to compile and run code to confirm the JDK is functional. This test exercises the compiler, the class loader, and the JVM in one sequence.
Here's how to create and run a minimal Java program to validate the setup.
cat > Test.java << 'EOF'
public class Test {
public static void main(String[] args) {
System.out.println("Java is working on Fedora");
}
}
EOF
# Creates a source file with a valid class definition and main method
# The heredoc writes the content without interpreting shell variables
Compile the source file. The compiler should produce a Test.class file without errors.
javac Test.java
# Compiles the source file into bytecode
# Produces Test.class in the current directory if successful
Run the compiled class. The JVM should load the bytecode and print the message.
java Test
# Executes the bytecode using the JVM
# Prints the output to stdout; no output means the class failed to load
You should see Java is working on Fedora printed to the terminal. If you get Error: Could not find or load main class Test, check that you are in the directory containing Test.class and that the class name matches the filename.
Clean up the test files. Leave no artifacts in your working directory.
rm Test.java Test.class
# Removes the test source and compiled bytecode
# Keeps your workspace clean after verification
Compile once, run anywhere, but only if the JDK is actually installed.
Common errors and firewall rules
The Error: JAVA_HOME is not defined message appears when build tools cannot find the environment variable. This happens if you set JAVA_HOME in a script that does not source your profile, or if you are running the command in a GUI application that does not inherit shell variables. GUI tools often read environment variables from ~/.profile or /etc/environment instead of ~/.bashrc. Set JAVA_HOME in ~/.profile if a desktop application needs it.
The javac: command not found error means the -devel package is missing. Run sudo dnf install java-21-openjdk-devel to fix it. Do not try to install javac as a standalone package. It is bundled inside the JDK development package.
If you see version conflict errors, check that java and javac point to the same version. Run alternatives --config java and alternatives --config javac to align them.
Java applications often listen on network ports. If you are running a service like Spring Boot on port 8080, the firewall blocks external connections by default. Open the port and reload the firewall.
Here's how to allow traffic to a Java service port. The --permanent flag saves the rule, and --reload applies it.
sudo firewall-cmd --permanent --add-port=8080/tcp
# Adds the port to the permanent configuration so it survives reboots
sudo firewall-cmd --reload
# Reloads the firewall to apply the permanent rule to the running system
# Always reload after rule changes; runtime and persistent configs diverge otherwise
Run firewall-cmd --reload after every rule change. The runtime configuration and persistent configuration diverge if you skip this step. Connections will fail until the next reboot if the reload is missing.
Choose the right tool for the job
Use java-21-openjdk-devel when you need to compile code or run modern frameworks that require the full JDK.
Use java-21-openjdk when you only need to run pre-compiled applications and want a smaller footprint.
Use alternatives --config when you have multiple versions installed and need to change the system-wide default.
Use JAVA_HOME environment variable when build tools like Maven or Gradle cannot locate the JDK automatically.
Use dnf search openjdk to discover available versions before installing, rather than guessing the package name.
Use ~/.profile for environment variables when desktop applications need to read them, since GUI sessions often ignore ~/.bashrc.