# Multiple Java versions on Windows: pin JAVA_HOME per service

> Keep JDK 17 and 21 side by side, pin JAVA_HOME in each Tomcat/service wrapper, and stop relying on a machine-wide PATH switch.

**Tested on**

- Build: 26100.33650
- Edition: Standard / Datacenter
- Architecture: x64
- Evidence: E1
- Verified: 2026-10-29

## The short answer

Do not switch Java globally with `setx JAVA_HOME` and hope every service picks it up. Pin the JDK **per service**:

```text
C:\Java\jdk-17\
C:\Java\jdk-21\
C:\apps\tomcat-a\setenv.bat   -> JAVA_HOME=C:\Java\jdk-17
C:\apps\tomcat-b\setenv.bat   -> JAVA_HOME=C:\Java\jdk-21
```

`PATH` is for your interactive shell. A Windows service gets its environment when it starts; changing the machine PATH does not retarget a running service.

## Install side by side

1. Download Temurin/Microsoft OpenJDK zips for each LTS.
2. Extract to versioned directories, not `C:\Program Files\Java\latest`.
3. Do not add either `bin` to the machine PATH unless you need a default CLI.
4. Verify each:

```powershell
& C:\Java\jdk-17\bin\java.exe -version
& C:\Java\jdk-21\bin\java.exe -version
```

Keep the full path in scripts; it is the only unambiguous selector.

## Tomcat per instance

Tomcat reads `JAVA_HOME` (or `JRE_HOME`) at startup. In each instance:

```bat
REM C:\apps\tomcat-a\bin\setenv.bat
set "JAVA_HOME=C:\Java\jdk-17"
set "CATALINA_BASE=C:\apps\tomcat-a"
```

```bat
REM C:\apps\tomcat-b\bin\setenv.bat
set "JAVA_HOME=C:\Java\jdk-21"
set "CATALINA_BASE=C:\apps\tomcat-b"
```

`setenv.bat` is per instance and survives upgrades better than editing `catalina.bat`. Keep `CATALINA_HOME` as the shared Tomcat install if you run multiple instances from one binary; keep `CATALINA_BASE` per instance (config, logs, webapps).

## Windows service

If Tomcat runs as a service, the service wrapper must capture the right environment. With the Tomcat service manager:

```bat
tomcat10w.exe //ES//TomcatA
```

Set the Java tab / JVM path to the versioned `jre`/`jdk`, or use a wrapper that reads `setenv.bat`. After changing, restart the service and verify the actual process:

```powershell
Get-CimInstance Win32_Process -Filter "Name='java.exe'" |
  Select-Object ProcessId, CommandLine
```

The command line should show the expected JDK path.

## Verify the app, not just java -version

```powershell
Invoke-WebRequest http://localhost:8080/ -UseBasicParsing | Select-Object StatusCode
Get-Content C:\apps\tomcat-a\logs\catalina.*.log -Tail 50
```

A Tomcat started with the wrong JDK may still boot, then fail on a class file version, TLS provider or GC option. Check the log’s “Java Home”/JVM line.

> **Caution: Do not edit the global JAVA_HOME to fix one app**
>
> Changing the machine-wide `JAVA_HOME` can break every other Java service on the next restart. Pin the version in the service/instance; leave the global value alone unless it is intentional.

## Upgrade path

1. Add the new JDK beside the old.
2. Test one instance with `setenv.bat`.
3. Run the app’s test suite and check TLS/keystore compatibility.
4. Move instances one at a time.
5. Remove the old JDK after rollback is no longer needed.

## Lab boundary

E1. The lab did not install a JDK or Tomcat; the commands are the per-service pattern to validate on Server 2025.

## Sources

- [Microsoft Learn — Install OpenJDK](https://learn.microsoft.com/en-us/java/openjdk/install)
- [Apache Tomcat — Windows service](https://tomcat.apache.org/tomcat-10.1-doc/windows-service-howto.html)
- [Apache Tomcat — setenv](https://tomcat.apache.org/tomcat-10.1-doc/RUNNING.txt)

---

Source: https://winbook.fyi/en/server/runtimes/java-multi-version/
