Skip to content

fix(core): compatibility with JDK 26 #406

fix(core): compatibility with JDK 26

fix(core): compatibility with JDK 26 #406

Workflow file for this run

name: CI
on:
pull_request:
push:
branches:
- main
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
# JDK 8 is the source of truth: the client ships as a Java 8 artifact
# (io.questdb:questdb-client) and is released from JDK 8, so on JDK 8 it must
# compile, the full test suite must pass, and the javadoc jar must build
# (-P javadoc attaches it at the package phase). The native libraries are no
# longer committed, so this job compiles libquestdb.so from source (hence the
# zstd submodule + cmake/nasm/build-essential toolchain) before the tests run.
build-jdk8:
name: Build, test & javadoc (JDK 8)
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Check out
uses: actions/checkout@v4
with:
# zstd is required to compile the native library.
submodules: recursive
- name: Set up JDK 11 (MRJAR versioned classes)
# Exports JAVA_HOME_11_X64. The JDK 8 build compiles src/main/java11 with
# it into META-INF/versions/11 (see the antrun execution in core/pom.xml);
# JDK 8 javac cannot compile that source root.
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "11"
- name: Set up JDK 8
# Last setup-java call wins JAVA_HOME: the build itself runs on JDK 8.
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "8"
cache: maven
- name: Install native build toolchain
run: sudo apt-get update && sudo apt-get install -y cmake nasm build-essential
- name: Build native libquestdb.so
# JAVA_HOME points at the JDK 8 above, so the lib is compiled against the
# Java 8 JNI headers -- the artifact's Java floor. Copy it into src
# resources (not target/) so it survives the `mvn clean` in the next step
# and gets packaged + loaded via the production bin/<platform> path.
# NOTE: this builds on ubuntu-latest for FUNCTIONAL testing only; the
# library's glibc runtime floor is validated separately by the
# `glibc-floor` job, which rebuilds in the release low-glibc container.
run: |
cd core
cmake -DCMAKE_BUILD_TYPE=Release -B cmake-build-release -S.
cmake --build cmake-build-release --config Release
test -f target/classes/io/questdb/client/bin-local/libquestdb.so
mkdir -p src/main/resources/io/questdb/client/bin/linux-x86-64
cp target/classes/io/questdb/client/bin-local/libquestdb.so \
src/main/resources/io/questdb/client/bin/linux-x86-64/libquestdb.so
- name: Compile, test, and build javadoc
env:
JAVA11_HOME: ${{ env.JAVA_HOME_11_X64 }}
run: mvn -B -ntp -P javadoc clean install
- name: Upload client jar for the cross-JDK smoke job
uses: actions/upload-artifact@v4
with:
name: questdb-client-jar
path: |
core/target/questdb-client-*.jar
!core/target/*-tests.jar
!core/target/*-javadoc.jar
!core/target/*-sources.jar
if-no-files-found: error
# The 1.3.5-1.3.7 releases shipped a JDK 8-built jar whose FdBig class links
# sun.misc.FDBigInteger (gone since Java 9) and whose module name degraded to
# the filename-derived "questdb.client". Both are only observable in the
# PACKAGED jar on a modern JDK, so take the jar built by build-jdk8 and prove
# on JDK 25 that (a) the automatic module name is io.questdb.client and
# (b) slow-path double formatting resolves the META-INF/versions/11 bridge.
# Runs the JDK 8-built jar (the shipped artifact) on every newer JDK the
# client must work on. The double formatter reaches into jdk.internal.math
# via the META-INF/versions/11 bridge, and a JDK-internal change there is
# only visible when the packaged jar is EXECUTED on that JDK: issue #96
# (JDK 26 made FDBigInteger package-private) passed every JDK 8/11/25 check
# and broke only at runtime on 26; JDK 27 compact object headers (JEP 450,
# default-on) then shifted the AccessibleObject.override offset the bridge
# relies on -- both are exercised here. The 27-ea entry is a forward canary:
# it should be green, and does not fail the workflow because EA is a moving
# target -- a red run means the next JDK needs attention.
mrjar-smoke:
name: MRJAR smoke (JDK 8 jar on JDK ${{ matrix.java }})
needs: build-jdk8
runs-on: ubuntu-latest
timeout-minutes: 15
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
java: ["25", "26"]
experimental: [false]
include:
- java: "27-ea"
experimental: true
steps:
- name: Check out
uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.java }}
- name: Download JDK 8-built client jar
uses: actions/download-artifact@v4
with:
name: questdb-client-jar
path: client-jar
- name: Check module name and run double-formatting smoke
run: |
jar_file=(client-jar/questdb-client-*.jar)
jar_file="${jar_file[0]}"
jar --describe-module --file "$jar_file" | tee module.txt
grep -q '^io\.questdb\.client@' module.txt
javac -cp "$jar_file" -d smoke-classes \
core/src/test/java/io/questdb/client/test/std/DoubleFormatSmoke.java
# The FdBig double formatter reaches jdk.internal.math.FDBigInteger by
# setting AccessibleObject.override at an offset Unsafe derives from the
# object header size. Run the smoke under each object-header layout so a
# regression to a hard-coded offset fails a BLOCKING leg (25, 26), not
# only the non-blocking 27-ea canary. Compact object headers (JEP 450)
# are a product flag on JDK 25/26 and default-on from 27; the
# +IgnoreUnrecognizedVMOptions guard keeps the flags harmless on any JDK.
run_smoke() { # <label> <jvm flags...>
local label="$1"; shift
echo "::group::DoubleFormatSmoke [$label]"
java "$@" -cp "$jar_file:smoke-classes" io.questdb.client.test.std.DoubleFormatSmoke
echo "::endgroup::"
}
run_smoke "default" -XX:+IgnoreUnrecognizedVMOptions
run_smoke "compact-headers" -XX:+IgnoreUnrecognizedVMOptions -XX:+UseCompactObjectHeaders
run_smoke "uncompressed-oops" -XX:+IgnoreUnrecognizedVMOptions -XX:-UseCompressedOops -XX:-UseCompressedClassPointers
# The client is also consumed as a submodule of the main questdb repo, which
# builds on JDK 25, and contributors build on the newest GA JDK (issue #96
# was "does not compile on JDK 26"). Guard against compile breakage on both
# (main + test sources, both modules) and confirm the javadoc jar builds too
# (-P javadoc attaches it at the package phase). Do NOT run the tests -- the
# parent repo runs them against a real server.
compile-modern-jdk:
name: Compile & javadoc smoke (JDK ${{ matrix.java }})
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
java: ["25", "26"]
steps:
- name: Check out
uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.java }}
cache: maven
- name: Compile (main + test) and build javadoc (no tests run)
run: mvn -B -ntp -P javadoc -DskipTests clean package
# GLIBC floor guard. The native libraries are built at release time in
# low-glibc manylinux containers (see maven_central_release.yml) and are NOT
# committed, so a floor regression is invisible to the functional test job
# above (it builds on ubuntu-latest, whose glibc is new enough to load almost
# anything). This job rebuilds the linux libraries in the SAME low-glibc
# environment as release and asserts the runtime floor with objdump, so a
# change that raises the floor (e.g. a new stat/fstat call pulling in
# stat@GLIBC_2.33 on a modern build host) fails the PR instead of silently
# shipping a library that cannot load on older distros.
#
# * linux-x86-64 -> GLIBC_2.14 (the intended floor: memcpy@GLIBC_2.14).
# * linux-aarch64 -> GLIBC_2.17 (the lowest floor glibc offers on aarch64).
#
# Uses manylinux_2_28 for both arches (stock Node 24, no glibc-2.17 shadow
# hack). The x86-64 floor is identical in manylinux2014 (2.17) and
# manylinux_2_28 (2.28) -- both resolve stat/fstat to the inline
# __xstat/__fxstat@GLIBC_2.2.5 wrappers -- so this validates the real shipped
# floor without the heavier manylinux2014 release toolchain.
glibc-floor:
name: GLIBC floor guard (${{ matrix.platform }})
strategy:
fail-fast: false
matrix:
include:
- platform: linux-x86-64
os: ubuntu-latest
image: quay.io/pypa/manylinux_2_28_x86_64
jdk_arch: x64
floor: "2.14"
cmake_args: ""
build_dir: cmake-build-release
- platform: linux-aarch64
os: ubuntu-22.04-arm
image: quay.io/pypa/manylinux_2_28_aarch64
jdk_arch: aarch64
floor: "2.17"
cmake_args: "-DCMAKE_TOOLCHAIN_FILE=./src/main/c/toolchains/linux-arm64.cmake"
build_dir: cmake-build-release-arm64
runs-on: ${{ matrix.os }}
timeout-minutes: 45
container:
image: ${{ matrix.image }}
steps:
- name: Check out
uses: actions/checkout@v4
with:
# zstd is required to compile the native library.
submodules: recursive
- name: Install tooling
# binutils provides objdump for the floor check; nasm/zstd are build deps.
run: |
yum update -y
yum install -y wget nasm zstd binutils
- name: Install Temurin JDK 8 (for jni.h)
# Build against the Java 8 JNI headers -- JDK 8 is the artifact's floor.
# The JDK version does not affect the glibc floor; it only supplies jni.h.
run: |
wget -v --timeout=180 -O jdk8.tar.gz \
"https://api.adoptium.net/v3/binary/latest/8/ga/linux/${{ matrix.jdk_arch }}/jdk/hotspot/normal/eclipse"
mkdir jdk8
tar xfz jdk8.tar.gz -C jdk8 --strip-components=1
echo "JAVA_HOME=$(pwd)/jdk8" >> "$GITHUB_ENV"
- name: Build native libquestdb.so
run: |
cd core
cmake ${{ matrix.cmake_args }} -DCMAKE_BUILD_TYPE=Release -B ${{ matrix.build_dir }} -S.
cmake --build ${{ matrix.build_dir }} --config Release
- name: Assert GLIBC floor
run: |
./.github/scripts/check-glibc-floor.sh \
core/target/classes/io/questdb/client/bin-local/libquestdb.so \
"${{ matrix.floor }}"