From 8d9d0204a5370522935252eaccb9295004ad4568 Mon Sep 17 00:00:00 2001 From: Jaromir Hamala Date: Fri, 7 Aug 2026 16:36:44 +0200 Subject: [PATCH 1/3] fix(build): restore io.questdb.client module name and fix NoClassDefFoundError on Java 9+ The 1.3.5-1.3.7 releases moved the release build from JDK 11 to JDK 8, which silently broke the published jar in two ways: * module-info.class was no longer compiled and no Automatic-Module-Name was set, so the module name degraded from io.questdb.client to the filename-derived questdb.client. * only the src/main/java8 FdBig bridge (sun.misc.FDBigInteger) was packaged; sun.misc.FDBigInteger does not exist on Java 9+, so slow-path double formatting (e.g. doubleColumn with extreme-exponent values) died with NoClassDefFoundError: sun/misc/FDBigInteger. Fix, keeping JDK 8 as the release build JDK: * pin Automatic-Module-Name: io.questdb.client in the jar manifest * package the src/main/java11 bridge (jdk.internal.math.FDBigInteger) into META-INF/versions/11 and mark the jar Multi-Release: true; JDK 8 builds compile it with a JDK 11+ from JAVA11_HOME (build fails fast if unset, instead of shipping a broken jar) * JarPackagingIT (failsafe) asserts the packaged jar's manifest, MRJAR layout, and FdBig linkage, and executes slow-path double formatting against the jar in a child JVM - regressions that unit tests cannot see because they run against target/classes * new CI job runs the JDK 8-built jar on JDK 25: checks the derived module name and the double-formatting smoke (DoubleFormatSmoke) * enforcer refuses mvn deploy -P maven-central-publish on a non-JDK 8 build, whose jar root would break Java 8 consumers Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 60 ++++++++ .github/workflows/maven_central_release.yml | 23 +++ CLAUDE.md | 8 + core/pom.xml | 115 ++++++++++++++ .../client/test/std/DoubleFormatSmoke.java | 67 +++++++++ .../client/test/std/JarPackagingIT.java | 142 ++++++++++++++++++ 6 files changed, 415 insertions(+) create mode 100644 core/src/test/java/io/questdb/client/test/std/DoubleFormatSmoke.java create mode 100644 core/src/test/java/io/questdb/client/test/std/JarPackagingIT.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6a0cd74..9dd89f36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,17 @@ jobs: # 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 @@ -60,8 +70,58 @@ jobs: 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. + mrjar-smoke-jdk25: + name: MRJAR smoke (JDK 8 jar on JDK 25) + needs: build-jdk8 + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check out + uses: actions/checkout@v4 + + - name: Set up JDK 25 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "25" + + - 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 + java -cp "$jar_file:smoke-classes" io.questdb.client.test.std.DoubleFormatSmoke + # The client is also consumed as a submodule of the main questdb repo, which # builds on JDK 25. Guard against JDK 25 compile breakage (main + test # sources, both modules) and confirm the javadoc jar builds on JDK 25 too diff --git a/.github/workflows/maven_central_release.yml b/.github/workflows/maven_central_release.yml index ffb92b3c..89392c53 100644 --- a/.github/workflows/maven_central_release.yml +++ b/.github/workflows/maven_central_release.yml @@ -465,7 +465,17 @@ jobs: with: ref: ${{ needs.resolve.outputs.source_sha }} + - name: Set up Java 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@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: temurin + java-version: "11" + - name: Set up Java 8 + # Last setup-java call wins JAVA_HOME: the build itself runs on JDK 8. uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: temurin @@ -487,6 +497,8 @@ jobs: mvn -B -ntp org.codehaus.mojo:versions-maven-plugin:2.16.2:set -DnewVersion="${RELEASE_VERSION}" -DprocessAllModules=true -DgenerateBackupPoms=false - name: Verify release artifact (full test suite, native libs bundled) + env: + JAVA11_HOME: ${{ env.JAVA_HOME_11_X64 }} run: | # Tests on -- this is the gate. The bundled linux-x86-64 native library # is exercised by the real test suite before anyone approves the publish. @@ -559,7 +571,16 @@ jobs: exit 1 fi + - name: Set up Java 11 (MRJAR versioned classes) + # Same as the verify job: the deploy below re-packages the jar on JDK 8 + # and needs JDK 11 for the META-INF/versions/11 classes. + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: temurin + java-version: "11" + - name: Set up Java 8 + # Last setup-java call wins JAVA_HOME: the build itself runs on JDK 8. uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: temurin @@ -637,6 +658,8 @@ jobs: - name: Upload signed bundle to Central (validate only, droppable) id: upload + env: + JAVA11_HOME: ${{ env.JAVA_HOME_11_X64 }} run: | # autoPublish=false + waitUntil=validated (set in core/pom.xml) makes the # build block ONLY on validation (VALIDATING -> VALIDATED, a few minutes; diff --git a/CLAUDE.md b/CLAUDE.md index 7d2e56b6..4c103cda 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -68,6 +68,14 @@ Build on a JDK 8 to validate the shipping artifact (the source-of-truth target); also confirm it compiles on a modern JDK (11+) before merging, the same two fronts CI guards. +Packaging on JDK 8 additionally needs `JAVA11_HOME` pointing at a JDK 11+: +the jar is a Multi-Release jar whose `META-INF/versions/11` classes (the +`src/main/java11` FdBig/Compat bridge) cannot be compiled by JDK 8 javac. +Without them a JDK 8-built jar throws `NoClassDefFoundError: +sun/misc/FDBigInteger` on Java 9+ (the 1.3.5–1.3.7 regression), so the build +fails fast instead of skipping. Test-only runs (`mvn -pl core test`) don't +need it. + The parent `questdb` repo's `local-client` profile pulls this module as a sub-module so server changes can build against unpublished client code; if you change client code, install it (or pass `-P local-client` in the parent) diff --git a/core/pom.xml b/core/pom.xml index ef130cd6..352c7ee5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -119,6 +119,12 @@ ${buildNumber} ${project.version} + + io.questdb.client + true @@ -185,9 +191,35 @@ ${test.exclude} + + **/*IT.class + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.5.3 + + + + integration-test + verify + + + + + false + + ${project.build.directory}/${project.build.finalName}.jar + ${project.build.testOutputDirectory} + + + @@ -416,6 +448,30 @@ maven-central-publish + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.0.0-M3 + + + enforce-publish-from-jdk8 + + enforce + + + + + [1.8,1.9) + questdb-client must be published from JDK 8; a JDK 11+ build produces a jar that breaks Java 8 consumers. + + + + + + org.sonatype.central central-publishing-maven-plugin @@ -519,10 +575,69 @@ 1.8 1.3.15 + + ${env.JAVA11_HOME} 1.8 + + + + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + compile-mrjar-versions-11 + prepare-package + + run + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.jetbrains diff --git a/core/src/test/java/io/questdb/client/test/std/DoubleFormatSmoke.java b/core/src/test/java/io/questdb/client/test/std/DoubleFormatSmoke.java new file mode 100644 index 00000000..17ca50d5 --- /dev/null +++ b/core/src/test/java/io/questdb/client/test/std/DoubleFormatSmoke.java @@ -0,0 +1,67 @@ +/*+***************************************************************************** + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (c) 2014-2019 Appsicle + * Copyright (c) 2019-2026 QuestDB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +package io.questdb.client.test.std; + +import io.questdb.client.std.str.StringSink; + +/** + * Standalone (no JUnit) smoke check that double formatting works against the + * packaged jar on the running JDK. The extreme-exponent values exercise the + * FdBig fallback of {@code Numbers.appendDouble0}, which resolves + * {@code FDBigInteger} from a different JDK-internal package on Java 8 vs 9+; + * a jar packaged with the wrong bridge dies here with + * {@code NoClassDefFoundError: sun/misc/FDBigInteger} (the 1.3.5-1.3.7 + * regression). Run by {@link JarPackagingIT} on the build JDK and by CI on + * JDK 25 against the JDK 8-built jar. + */ +public final class DoubleFormatSmoke { + + public static void main(String[] args) { + double[] values = { + 0.0d, + 123.456d, + // FdBig slow-path values + 1.0E-300, + Double.MIN_VALUE, + Double.MAX_VALUE, + -2.225073858507201E-308, + 1.1317400099603851E308 + }; + for (int i = 0; i < values.length; i++) { + double d = values[i]; + StringSink sink = new StringSink(); + sink.put(d); + String formatted = sink.toString(); + if (Double.doubleToLongBits(Double.parseDouble(formatted)) != Double.doubleToLongBits(d)) { + System.err.println("FAIL: " + d + " formatted as \"" + formatted + "\" does not round-trip"); + System.exit(1); + } + } + System.out.println("OK: double formatting works on Java " + System.getProperty("java.version")); + } + + private DoubleFormatSmoke() { + } +} diff --git a/core/src/test/java/io/questdb/client/test/std/JarPackagingIT.java b/core/src/test/java/io/questdb/client/test/std/JarPackagingIT.java new file mode 100644 index 00000000..4184187b --- /dev/null +++ b/core/src/test/java/io/questdb/client/test/std/JarPackagingIT.java @@ -0,0 +1,142 @@ +/*+***************************************************************************** + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (c) 2014-2019 Appsicle + * Copyright (c) 2019-2026 QuestDB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +package io.questdb.client.test.std; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * Guards the packaged jar against the 1.3.5-1.3.7 release regressions, which + * unit tests cannot see because they run against target/classes where neither + * the manifest nor Multi-Release versioned-class selection exists: + *
    + *
  • the module name silently changing from {@code io.questdb.client} to the + * filename-derived {@code questdb.client} (no module-info.class on JDK 8 + * builds and no Automatic-Module-Name),
  • + *
  • JDK 8 builds shipping only the {@code sun.misc.FDBigInteger} FdBig + * bridge, which throws {@code NoClassDefFoundError} on Java 9+ without the + * {@code META-INF/versions/11} counterpart.
  • + *
+ */ +public class JarPackagingIT { + private static final String FD_BIG_ENTRY = "io/questdb/client/std/FdBig.class"; + private static final String VERSIONED_FD_BIG_ENTRY = "META-INF/versions/11/" + FD_BIG_ENTRY; + + @Test + public void testDoubleFormattingAgainstPackagedJar() throws Exception { + String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; + String classpath = jarPath() + File.pathSeparator + System.getProperty("questdb.client.test.classes"); + Process process = new ProcessBuilder(javaBin, "-cp", classpath, DoubleFormatSmoke.class.getName()) + .redirectErrorStream(true) + .start(); + String output = readFully(process.getInputStream()); + int exitCode = process.waitFor(); + Assert.assertEquals("double formatting against the packaged jar failed:\n" + output, 0, exitCode); + } + + @Test + public void testJarLayout() throws Exception { + try (JarFile jar = new JarFile(jarPath())) { + Attributes attrs = jar.getManifest().getMainAttributes(); + Assert.assertEquals("io.questdb.client", attrs.getValue("Automatic-Module-Name")); + Assert.assertEquals("true", attrs.getValue("Multi-Release")); + + if ("1.8".equals(System.getProperty("java.specification.version"))) { + // JDK 8 build: the shipping layout. Root classes target Java 8, + // the java11 bridge rides in META-INF/versions/11. + Assert.assertTrue( + "root FdBig of a JDK 8 build must use sun.misc.FDBigInteger", + classReferences(jar, FD_BIG_ENTRY, "sun/misc/FDBigInteger") + ); + Assert.assertTrue( + "META-INF/versions/11 FdBig must use jdk.internal.math.FDBigInteger", + classReferences(jar, VERSIONED_FD_BIG_ENTRY, "jdk/internal/math/FDBigInteger") + ); + Assert.assertNotNull( + "META-INF/versions/11 must carry the java11 Compat shim", + jar.getEntry("META-INF/versions/11/io/questdb/client/std/Compat.class") + ); + } else { + // JDK 11+ build: dev/smoke only, never shipped. Root classes are the + // java11 variants and the real module descriptor is present. + Assert.assertTrue( + "root FdBig of a JDK 11+ build must use jdk.internal.math.FDBigInteger", + classReferences(jar, FD_BIG_ENTRY, "jdk/internal/math/FDBigInteger") + ); + Assert.assertNotNull("module-info.class missing", jar.getEntry("module-info.class")); + } + } + } + + private static boolean classReferences(JarFile jar, String entryName, String constant) throws IOException { + JarEntry entry = jar.getJarEntry(entryName); + Assert.assertNotNull("jar entry missing: " + entryName, entry); + byte[] classBytes; + try (InputStream in = jar.getInputStream(entry)) { + classBytes = readAll(in); + } + // the referenced class name appears verbatim as a constant-pool UTF-8 entry + byte[] needle = constant.getBytes("UTF-8"); + for (int i = 0; i <= classBytes.length - needle.length; i++) { + int j = 0; + while (j < needle.length && classBytes[i + j] == needle[j]) { + j++; + } + if (j == needle.length) { + return true; + } + } + return false; + } + + private static String jarPath() { + String path = System.getProperty("questdb.client.jar"); + Assert.assertNotNull("questdb.client.jar system property not set", path); + Assert.assertTrue("packaged jar not found: " + path, new File(path).exists()); + return path; + } + + private static byte[] readAll(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[8192]; + int n; + while ((n = in.read(buffer)) != -1) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } + + private static String readFully(InputStream in) throws IOException { + return new String(readAll(in), "UTF-8"); + } +} From 45b596221e21c86e48298c2171fa6635e43bad3c Mon Sep 17 00:00:00 2001 From: Jaromir Hamala Date: Fri, 7 Aug 2026 17:09:22 +0200 Subject: [PATCH 2/3] fix(build): compile all of src/main/java11 into META-INF/versions/11 Replace the hard-coded two-file javac invocation with a whole-directory ant , so a file added to src/main/java11 cannot silently ship without its versions/11 counterpart. JarPackagingIT now also cross-checks every source in src/main/java11 against the packaged jar. Co-Authored-By: Claude Fable 5 --- core/pom.xml | 25 +++++++------------ .../client/test/std/JarPackagingIT.java | 24 ++++++++++++++++++ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 352c7ee5..91a4d686 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -217,6 +217,7 @@ ${project.build.directory}/${project.build.finalName}.jar ${project.build.testOutputDirectory} + ${project.basedir}/src/main/java11
@@ -615,22 +616,14 @@ - - - - - - - - - - - - - - - - + + + + + diff --git a/core/src/test/java/io/questdb/client/test/std/JarPackagingIT.java b/core/src/test/java/io/questdb/client/test/std/JarPackagingIT.java index 4184187b..77fec46a 100644 --- a/core/src/test/java/io/questdb/client/test/std/JarPackagingIT.java +++ b/core/src/test/java/io/questdb/client/test/std/JarPackagingIT.java @@ -86,6 +86,15 @@ public void testJarLayout() throws Exception { "META-INF/versions/11 must carry the java11 Compat shim", jar.getEntry("META-INF/versions/11/io/questdb/client/std/Compat.class") ); + // every source in src/main/java11 must ship in versions/11 -- a file added + // to the source root but missed by the packaging step would recreate the + // NoClassDefFoundError class of bug on Java 9+ + File java11SrcRoot = new File(System.getProperty("questdb.client.java11.src")); + Assert.assertTrue("src/main/java11 not found: " + java11SrcRoot, java11SrcRoot.isDirectory()); + for (String relativeSource : collectJavaSources(java11SrcRoot, "")) { + String entry = "META-INF/versions/11/" + relativeSource.replaceAll("\\.java$", ".class"); + Assert.assertNotNull("src/main/java11/" + relativeSource + " has no packaged counterpart " + entry, jar.getEntry(entry)); + } } else { // JDK 11+ build: dev/smoke only, never shipped. Root classes are the // java11 variants and the real module descriptor is present. @@ -98,6 +107,21 @@ public void testJarLayout() throws Exception { } } + private static java.util.List collectJavaSources(File dir, String prefix) { + java.util.List result = new java.util.ArrayList(); + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + result.addAll(collectJavaSources(file, prefix + file.getName() + "/")); + } else if (file.getName().endsWith(".java")) { + result.add(prefix + file.getName()); + } + } + } + return result; + } + private static boolean classReferences(JarFile jar, String entryName, String constant) throws IOException { JarEntry entry = jar.getJarEntry(entryName); Assert.assertNotNull("jar entry missing: " + entryName, entry); From cbd0106bb13c3694ef71bf4f4c342e7efe67ba23 Mon Sep 17 00:00:00 2001 From: Jaromir Hamala Date: Fri, 7 Aug 2026 17:54:10 +0200 Subject: [PATCH 3/3] test(build): execute the versions/11 bridge on every packaging build JarPackagingIT ran DoubleFormatSmoke only on the build JDK, so on JDK 8 builds -- including the release verify gate -- the META-INF/versions/11 classes were validated structurally but never executed; the only executable proof lived in the CI smoke job, which never sees the release-built jar. The IT now spawns a second child JVM from JAVA11_HOME (always present during JDK 8 packaging) so the versioned bridge is run against the exact jar being shipped. Co-Authored-By: Claude Fable 5 --- .../client/test/std/JarPackagingIT.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/io/questdb/client/test/std/JarPackagingIT.java b/core/src/test/java/io/questdb/client/test/std/JarPackagingIT.java index 77fec46a..549768a7 100644 --- a/core/src/test/java/io/questdb/client/test/std/JarPackagingIT.java +++ b/core/src/test/java/io/questdb/client/test/std/JarPackagingIT.java @@ -54,14 +54,32 @@ public class JarPackagingIT { @Test public void testDoubleFormattingAgainstPackagedJar() throws Exception { - String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; + runSmokeAgainstJar(System.getProperty("java.home")); + if ("1.8".equals(System.getProperty("java.specification.version"))) { + // On a JDK 8 build the run above only exercised the root sun.misc + // classes. Run again on the JDK 11+ that compiled the versioned + // bridge, so every packaging build -- including the release verify + // gate -- EXECUTES the META-INF/versions/11 classes instead of only + // checking they exist. Required, not skipped-if-absent: packaging + // already failed earlier without a JDK 11 (see the antrun step). + String bridgeJdkHome = System.getProperty("java11.home"); + if (bridgeJdkHome == null || bridgeJdkHome.isEmpty() || bridgeJdkHome.startsWith("${")) { + bridgeJdkHome = System.getenv("JAVA11_HOME"); + } + Assert.assertNotNull("JAVA11_HOME (or -Djava11.home) must point at a JDK 11+", bridgeJdkHome); + runSmokeAgainstJar(bridgeJdkHome); + } + } + + private static void runSmokeAgainstJar(String jdkHome) throws Exception { + String javaBin = jdkHome + File.separator + "bin" + File.separator + "java"; String classpath = jarPath() + File.pathSeparator + System.getProperty("questdb.client.test.classes"); Process process = new ProcessBuilder(javaBin, "-cp", classpath, DoubleFormatSmoke.class.getName()) .redirectErrorStream(true) .start(); String output = readFully(process.getInputStream()); int exitCode = process.waitFor(); - Assert.assertEquals("double formatting against the packaged jar failed:\n" + output, 0, exitCode); + Assert.assertEquals("double formatting against the packaged jar failed on " + javaBin + ":\n" + output, 0, exitCode); } @Test