RHEL 8 ships multiple Java Development Kits through the AppStream module, allowing teams to run Java 11 and Java 17 workloads side by side on the same host without conflicts. The alternatives system acts as a switchboard, letting administrators change the active JDK globally or per user in seconds. This tutorial walks through installing Java 11 and Java 17, configuring the alternatives symlinks, deriving JAVA_HOME dynamically from the active javac, using that variable with Maven and Gradle, and passing JAVA_HOME correctly to a systemd service unit. Following these steps ensures that every tool in your stack — from the shell to the init system — refers to the same JDK.

Prerequisites

  • A RHEL 8 server with a non-root sudo user
  • Active RHEL subscription or CentOS Stream 8 / AlmaLinux 8 equivalent
  • At least 1.5 GB of free disk space under /usr
  • sudo or root access to write to /etc/profile.d/ and /etc/systemd/system/

Step 1 — Install Java 11 and Java 17 JDKs

Both JDKs are available from the default RHEL 8 AppStream repository. Installing the -devel packages provides the full compiler toolchain rather than the runtime-only JRE.

sudo dnf install java-11-openjdk-devel java-17-openjdk-devel -y

# Confirm both are installed
rpm -qa | grep java-1[17]-openjdk-devel

# List all registered java alternatives
alternatives --list | grep java

Step 2 — Switch the Active JDK with the Alternatives System

The alternatives system maintains independent symlinks for the java runtime and the javac compiler. Both must be switched together when changing the active JDK.

# Select the active java runtime interactively
sudo alternatives --config java
# Enter the number corresponding to Java 17

# Select the active compiler
sudo alternatives --config javac

# Verify both point to the same JDK version
java -version
javac -version

Step 3 — Set JAVA_HOME Dynamically via /etc/profile.d

Hard-coding a JDK path in JAVA_HOME breaks whenever alternatives is switched. The following snippet derives the path at shell startup by resolving the double symlink that alternatives creates.

sudo tee /etc/profile.d/java.sh << 'EOF'
# Resolve the real JDK root from the active javac alternative
export JAVA_HOME=$(dirname $(dirname $(readlink $(readlink $(which javac)))))
export PATH=$PATH:$JAVA_HOME/bin
EOF

source /etc/profile.d/java.sh
echo $JAVA_HOME
# Expected: /usr/lib/jvm/java-17-openjdk-17.x.x.x-x.el8.x86_64
java -version

Step 4 — Use JAVA_HOME with Maven and Gradle

Both build tools discover the JDK through JAVA_HOME. Install them via DNF or their respective install scripts and confirm they pick up the correct JDK.

# Install Maven from AppStream
sudo dnf install maven -y

mvn --version
# Apache Maven 3.x.x  Java version: 17.x.x  Java home: /usr/lib/jvm/java-17-...

# Install Gradle manually (Gradle is not in AppStream)
GRADLE_VERSION=8.7
wget https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip -P /tmp
sudo unzip /tmp/gradle-${GRADLE_VERSION}-bin.zip -d /opt
sudo ln -s /opt/gradle-${GRADLE_VERSION}/bin/gradle /usr/local/bin/gradle

gradle --version
# Gradle 8.7  JVM: 17.x.x (Red Hat)

Step 5 — Run a Java Application as a systemd Service

When systemd launches a service it does not source /etc/profile.d/, so JAVA_HOME must be declared explicitly inside the unit file using an Environment= directive.

# Capture the current JAVA_HOME to embed in the unit
echo $JAVA_HOME
# /usr/lib/jvm/java-17-openjdk-17.x.x.x-x.el8.x86_64

sudo tee /etc/systemd/system/myapp.service << 'EOF'
[Unit]
Description=My Java Application
After=network.target

[Service]
User=myappuser
WorkingDirectory=/opt/myapp
Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk-17.x.x.x-x.el8.x86_64
ExecStart=/usr/lib/jvm/java-17-openjdk-17.x.x.x-x.el8.x86_64/bin/java 
    -jar /opt/myapp/myapp.jar
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
sudo systemctl status myapp.service

Step 6 — Switching JDKs and Verifying the Whole Stack

After switching alternatives, re-source the profile script and verify that Maven, Gradle, and your application unit all reflect the new JDK.

# Switch to Java 11
sudo alternatives --config java   # choose Java 11
sudo alternatives --config javac

source /etc/profile.d/java.sh
echo $JAVA_HOME    # now points to java-11-openjdk
java -version      # 11.x.x
javac -version     # 11.x.x
mvn --version      # Java version: 11
gradle --version   # JVM: 11

# Update the systemd unit's Environment= line to match, then reload
sudo systemctl daemon-reload
sudo systemctl restart myapp.service

Conclusion

You have installed OpenJDK 11 and OpenJDK 17 on RHEL 8, used the alternatives system to switch the active JDK, configured a dynamic JAVA_HOME through /etc/profile.d/java.sh, confirmed that Maven and Gradle inherit the correct JDK, and embedded an explicit Environment=JAVA_HOME directive in a systemd service unit. This approach keeps your environment consistent across interactive shells, build tools, and the init system regardless of which JDK version is active.

Next steps: How to Build and Deploy a Spring Boot Application on RHEL 8, How to Tune JVM Heap and GC Settings for Production on RHEL 8, and How to Containerise a Java Application with Podman on RHEL 8.