env.dev

JAVA_HOME

The path to the root of a JDK installation. Build tools like Maven and Gradle and servers like Tomcat use it to pick which Java to run with — the java command itself never reads it; PATH decides that.

Last updated:

JAVA_HOME points at the root directory of a JDK installation — the folder that contains bin/, not bin/ itself. The java launcher never reads it: which Java runs when you type `java` is decided by PATH alone. What reads JAVA_HOME is the tooling layer around the JDK: Maven's mvn script refuses to start without a usable JDK and resolves the compiler through it, Gradle uses it to pick the JVM that runs the build (unless org.gradle.java.home or a toolchain overrides it), and Tomcat's catalina.sh locates the JVM through JAVA_HOME or JRE_HOME. That split is why `java -version` and your build tool can disagree about which JDK is in use.

Provider
General / OS
Category
runtime
Set by
Set manually in shell profiles, by SDKMAN!/jEnv, or by Windows JDK installers
Example
/usr/lib/jvm/java-21-openjdk-amd64
Gotcha: The most common mistake is pointing JAVA_HOME at the bin directory (JAVA_HOME=/usr/lib/jvm/jdk-21/bin) or at the java binary itself. Tools append /bin/java to whatever you set, so both produce 'No such file or directory' or Maven's 'JAVA_HOME should point to a JDK not a JRE'. On Windows, do not wrap the value in quotes in the System Properties dialog — JAVA_HOME="C:\Program Files\Java\jdk-21" with literal quotes breaks every script that concatenates %JAVA_HOME%\bin.

How to set JAVA_HOME

macOS (resolve the active JDK)

export JAVA_HOME=$(/usr/libexec/java_home -v 21)

Linux (Debian/Ubuntu OpenJDK path)

export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH="$JAVA_HOME/bin:$PATH"

Windows (PowerShell, current session)

$env:JAVA_HOME = "C:\Program Files\Java\jdk-21"

verify what your build actually uses

mvn -version   # shows the JAVA_HOME JDK
java -version  # shows the PATH JDK — these can differ

Why do java and Maven disagree about my Java version?

Because they resolve the JDK through two unrelated mechanisms. java on the command line is found via PATH — first matching binary wins. Maven's mvn launcher script explicitly uses $JAVA_HOME/bin/java when JAVA_HOME is set. Install a new JDK that updates one but not the other and you get the canonical confusion:

bash
$ java -version        # resolved via PATH
openjdk version "21.0.3"

$ mvn -version          # resolved via JAVA_HOME
Apache Maven 3.9.6
Java version: 17.0.10, vendor: Eclipse Adoptium

The fix is to derive both from the same root, in this order, in your shell profile:

bash
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export PATH="$JAVA_HOME/bin:$PATH"

What reads JAVA_HOME — and what doesn't

  • Reads it: Maven (hard requirement — the launcher errors with "JAVA_HOME should point to a JDK, not a JRE" when misconfigured), Gradle (picks the JVM that runs the build unless org.gradle.java.home overrides it), Tomcat's catalina.sh (JRE_HOME takes precedence if both are set), Jenkins agents, Android tooling, and most IDE launcher scripts.
  • Ignores it: the java and javac binaries themselves. JAVA_HOME is a convention consumed by the tooling layer, never by the JVM launcher — which is exactly why the two can disagree.

Gradle has been quietly reducing JAVA_HOME's importance: toolchains (Gradle 6.7+) let the build declare "I need Java 17" and auto-provision a matching JDK, so the JVM compiling your code may be neither the PATH one nor the JAVA_HOME one. When debugging a Gradle build, trust gradle -version and the toolchain report over any environment variable.

Setting it per OS without hardcoding paths

bash
# macOS — never hardcode /Library/Java/...; ask the resolver:
export JAVA_HOME=$(/usr/libexec/java_home -v 21)

# Debian/Ubuntu — list candidates, then pin:
update-java-alternatives --list
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64

# Anywhere — let a version manager own it:
sdk use java 21.0.3-tem   # SDKMAN! exports JAVA_HOME for you

On Windows, JDK installers (Adoptium's MSI has an explicit "Set JAVA_HOME" checkbox) write it to the system environment. Two Windows-specific traps: don't include quotes in the value itself — scripts that expand %JAVA_HOME%\bin end up with quotes mid-path — and after editing System Properties, only newly opened terminals see the change.

JDK root, not bin — the off-by-one-directory bug

Every consumer of JAVA_HOME appends paths like /bin/java or /lib/tools.jar to it. Point it at .../jdk-21/bin and tools look for .../jdk-21/bin/bin/java; point it at the binary itself and they look for .../java/bin/java. The quick self-check is one line: "$JAVA_HOME/bin/java" -version — if that fails, every downstream tool will too. In Docker images the equivalent is checking the base image's convention first: the official eclipse-temurin images already set JAVA_HOME and PATH correctly, so re-setting them in your Dockerfile is usually copy-paste cargo culting. How ENV layering works there is covered in the Docker environment variables guide.

Reading config from Java itself

Inside the JVM, System.getenv("JAVA_HOME") returns whatever the process inherited — but the honest source of truth for "which Java am I running" is the system property System.getProperty("java.home"), which the JVM computes from its own install location and which cannot lie. The broader pattern of environment variables vs system properties in Java — including why -D flags and env vars get confused — is the subject of the Java environment variables guide.

Frequently Asked Questions

Do I need JAVA_HOME if java already works in my terminal?

For running java directly, no — PATH is enough. You need JAVA_HOME the moment Maven, Gradle, Tomcat, Jenkins, or an IDE launcher is involved, because those resolve the JDK through JAVA_HOME instead of PATH. Setting both, consistently, from the same JDK path avoids the two diverging.

Should JAVA_HOME include the bin directory?

No. Set it to the JDK root — the directory that contains bin/, lib/, and release. Tools build paths like $JAVA_HOME/bin/java themselves, so including /bin produces paths like .../bin/bin/java and fails.

Was this helpful?

Stay up to date

Get notified about new guides, tools, and cheatsheets.

Browse all 244 environment variables →