Skip to content

Pin Java toolchain to the JDK found by find_package(Java) - #6561

Merged
hyoklee merged 1 commit into
HDFGroup:developfrom
hyoklee:fix-java-toolchain-jdk-mismatch
Aug 6, 2026
Merged

Pin Java toolchain to the JDK found by find_package(Java)#6561
hyoklee merged 1 commit into
HDFGroup:developfrom
hyoklee:fix-java-toolchain-jdk-mismatch

Conversation

@hyoklee

@hyoklee hyoklee commented Jul 24, 2026

Copy link
Copy Markdown
Member

Close #6559.

Problem

When HDF5_BUILD_JAVA=ON, HDF5 discovers Java twice, through two CMake
mechanisms with opposite search precedence, and never reconciles them:

  1. find_package(Java) in CMakeLists.txtJava_JAVA_EXECUTABLE,
    Java_JAVAC_EXECUTABLE, Java_JAR_EXECUTABLE, Java_VERSION_STRING
  2. project(HDF5_JAVA C Java) in java/CMakeLists.txtCMAKE_Java_COMPILER,
    CMAKE_Java_RUNTIME, CMAKE_Java_ARCHIVE

FindJava.cmake puts JAVA_HOME in HINTS, which outrank $PATH, and
honors both $ENV{JAVA_HOME} and -DJAVA_HOME= via CMakeFindJavaCommon.cmake.
CMakeDetermineJavaCompiler.cmake puts $ENV{JAVA_HOME}/bin in PATHS, the
lowest-priority slot, searched after $PATH, and ignores the CMake variable
JAVA_HOME entirely.

So when JAVA_HOME and $PATH name different JDKs, the two diverge. HDF5 then
evaluates the FFM/JNI version gate in java/CMakeLists.txt

if (Java_VERSION_STRING VERSION_GREATER_EQUAL "25.0.0")

against one JDK while compiling and testing with another.

This is not macOS-specific. On Windows with PATH → JDK 11 and JAVA_HOME
JDK 20, current develop reports:

-- Found Java: .../OpenJDK/jdk-20/bin/java.exe (found version "20.0.0")
-- Building HDF5 Java with JNI implementation (Java 20)

CMAKE_Java_COMPILER ".../Microsoft/jdk-11.0.16.101-hotspot/bin/javac.exe"
CMAKE_Java_RUNTIME  ".../Microsoft/jdk-11.0.16.101-hotspot/bin/java.exe"
TEST_JAVA=.../Microsoft/jdk-11.0.16.101-hotspot/bin/java.exe

macOS merely makes it lethal rather than latent: both lookups land on the
/usr/bin stubs, which are not JDKs but dispatchers that re-select one from
$JAVA_HOME at each invocation. javac and java can therefore resolve to
different JDKs inside a single build, and every Java test dies at class load:

-- JAVA COMMAND:  /usr/bin/java; -Xmx1024M ...
Exception in thread "main" java.lang.UnsupportedClassVersionError:
  test/TestH5Eparams has been compiled by a more recent version of the Java
  Runtime (class file version 69.0), this version of the Java Runtime only
  recognizes class file versions up to 65.0

(69 = JDK 25, 65 = JDK 21.)

Fix

Propagate the toolchain from find_package(Java) before the Java language is
enabled, filling each variable independently.

Independently matters. The guard in CMakeDetermineJavaCompiler.cmake is
if(NOT CMAKE_Java_COMPILER) and it closes only after the runtime and archive
lookups, so a caller doing

cmake -DJAVA_HOME=$JAVA_HOME -DCMAKE_Java_COMPILER=$JAVA_HOME/bin/javac ...

suppresses the other two lookups entirely and ends up with an empty
CMAKE_Java_RUNTIME and an empty TEST_JAVA in the generated CTest files — so
ctest invokes no JVM at all. Filling the three in one at a time keeps an explicit
override working while still completing the toolchain.

Verification

Windows, PATH → JDK 11, JAVA_HOME → JDK 20: compiler, runtime, archiver
and TEST_JAVA now all resolve to JDK 20, matching the Java_VERSION_STRING
that drove the implementation choice.

macOS 26.5.2 arm64 (shared / OpenMPI / Fortran / Java), JAVA_HOME → JDK 25:
the same build went from 95 failing Java tests to 0.

GitHub macos-26, three configurations — image-default JAVA_HOME;
JAVA_HOME → 25 with only -DCMAKE_Java_COMPILER passed; and JAVA_HOME → 25
with JDK 21 first on $PATH — all produce a single-JDK toolchain and 255/255
Java tests passing. This includes the ctest -D ExperimentalConfigure path,
where CMake is re-run in a shell whose JAVA_HOME has reverted to the image
default; the pin survives because -DJAVA_HOME= is a cache entry and
CMakeFindJavaCommon.cmake consults the CMake variable before
$ENV{JAVA_HOME}.

Notes

  • Behavior is unchanged when JAVA_HOME and $PATH already agree, which is the
    common case; the added block only fills variables that are otherwise unset.
  • An explicit -DCMAKE_Java_COMPILER= / -DCMAKE_Java_RUNTIME= still wins.
  • Not addressed here: if JAVA_HOME is unset entirely on macOS,
    find_package(Java) itself falls through to $PATH and finds /usr/bin/java,
    so the toolchain is pinned to a stub. It is at least pinned consistently, but
    hardening that would mean rejecting /usr/bin runtimes or resolving through
    /usr/libexec/java_home at configure time.
  • No release_docs/RELEASE.txt entry is included; happy to add one if that is
    wanted for a build-system fix.

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Checklist

This PR touches the following areas. Each needs a sign-off
from its listed owners before merging.

✅ All areas have been signed off.

HDF5 discovers Java twice, through mechanisms with opposite search
precedence, and never reconciles them:

  find_package(Java) in CMakeLists.txt sets Java_JAVA_EXECUTABLE,
  Java_JAVAC_EXECUTABLE and Java_VERSION_STRING. FindJava searches
  JAVA_HOME via HINTS, which outrank $PATH.

  project(HDF5_JAVA C Java) in java/CMakeLists.txt sets
  CMAKE_Java_COMPILER/RUNTIME/ARCHIVE. CMakeDetermineJavaCompiler searches
  $ENV{JAVA_HOME}/bin only as a PATHS entry, the lowest priority slot,
  below $PATH, and ignores the CMake variable JAVA_HOME entirely.

When JAVA_HOME and PATH refer to different JDKs the two diverge, so the
FFM/JNI version gate in java/CMakeLists.txt is evaluated against one JDK
while the code is compiled and tested with another. On macOS both resolve
to the /usr/bin stubs, which re-select a JDK from JAVA_HOME on every
invocation, so javac and java can differ inside a single build and every
Java test fails to load its class files with UnsupportedClassVersionError.

Propagate the probed toolchain before the Java language is enabled, filling
each variable independently. The guard in CMakeDetermineJavaCompiler.cmake
is if(NOT CMAKE_Java_COMPILER) and it closes only after the runtime and
archive lookups, so a caller passing just

  -DJAVA_HOME=$JAVA_HOME -DCMAKE_Java_COMPILER=$JAVA_HOME/bin/javac

suppresses the other two lookups entirely and ends up with an empty
CMAKE_Java_RUNTIME and an empty TEST_JAVA in the generated CTest files.
Filling them one at a time keeps an explicit override working while still
completing the toolchain.

Verified two ways. Configuring with PATH -> JDK 11 and JAVA_HOME -> JDK 20
previously reported Java 20 and selected the JNI implementation while
CMAKE_Java_COMPILER, CMAKE_Java_RUNTIME and the generated TEST_JAVA all
pointed at JDK 11; they now all resolve to JDK 20. On macOS 26 arm64 with
JAVA_HOME -> JDK 25 the same build went from 95 failing Java tests to none.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@hyoklee
hyoklee force-pushed the fix-java-toolchain-jdk-mismatch branch from 124b71f to 923a29e Compare July 24, 2026 00:31

@jhendersonHDF jhendersonHDF left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than override the CMAKE_Java_XXX variables, we should probably remove Java from the list of languages specified in the Java-specific CMakeLists files and also push down this find_package call into those files. See the last comment on https://gitlab.kitware.com/cmake/cmake/-/work_items/18174. Also, the CMake documentation doesn't list support for Java as a language: https://cmake.org/cmake/help/latest/command/project.html#options.

@hyoklee
hyoklee merged commit a5827a6 into HDFGroup:develop Aug 6, 2026
137 checks passed
@github-project-automation github-project-automation Bot moved this from To be triaged to Done in HDF5 - TRIAGE & TRACK Aug 6, 2026
@hyoklee
hyoklee deleted the fix-java-toolchain-jdk-mismatch branch August 6, 2026 02:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

CMake: Java toolchain is discovered independently of find_package(Java), allowing compiler and runtime to be different JDKs

3 participants