This is an automated email from the ASF dual-hosted git repository.

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar.git


The following commit(s) were added to refs/heads/master by this push:
     new 1a5dff6a3e7 [improve][build] Support building and testing with Java 25 
(#25453)
1a5dff6a3e7 is described below

commit 1a5dff6a3e790548030c42f28880653e2e3498d1
Author: Lari Hotari <[email protected]>
AuthorDate: Wed Apr 1 18:18:36 2026 +0300

    [improve][build] Support building and testing with Java 25 (#25453)
---
 .github/actions/setup-gradle/action.yml            | 11 ++++++
 .github/actions/tune-runner-vm/action.yml          |  4 +--
 .github/workflows/codeql.yaml                      |  4 +--
 .github/workflows/pulsar-ci-flaky.yaml             |  8 ++---
 .github/workflows/pulsar-ci.yaml                   | 33 +++++++----------
 README.md                                          |  5 ++-
 .../main/kotlin/pulsar.java-conventions.gradle.kts |  9 +++++
 distribution/server/src/assemble/LICENSE.bin.txt   |  2 +-
 distribution/shell/src/assemble/LICENSE.bin.txt    |  2 +-
 gradle/libs.versions.toml                          |  3 +-
 microbench/README.md                               | 41 ++++++++++++++++------
 pulsar-build/run_integration_group_gradle.sh       |  3 +-
 settings.gradle.kts                                | 17 +++++++++
 .../integration/offload/TestFileSystemOffload.java | 11 ++++++
 .../integration/offload/TestOffloadDeletionFS.java | 11 ++++++
 tiered-storage/file-system/build.gradle.kts        |  9 +++++
 16 files changed, 128 insertions(+), 45 deletions(-)

diff --git a/.github/actions/setup-gradle/action.yml 
b/.github/actions/setup-gradle/action.yml
index c7f2518a232..ceac3840231 100644
--- a/.github/actions/setup-gradle/action.yml
+++ b/.github/actions/setup-gradle/action.yml
@@ -39,13 +39,24 @@ inputs:
 runs:
   using: composite
   steps:
+    - name: Set Develocity Project ID and configure custom settings
+      if: ${{ inputs.develocity-access-key != '' && inputs.build-scan-publish 
== 'true' }}
+      shell: bash
+      run: |
+        mkdir -p ~/.gradle
+        touch ~/.gradle/gradle.properties
+        grep -q 'systemProp.develocity.projectId=' ~/.gradle/gradle.properties 
|| echo systemProp.develocity.projectId=pulsar >> ~/.gradle/gradle.properties
+        grep -q 'systemProp.scan.uploadInBackground=' 
~/.gradle/gradle.properties || echo systemProp.scan.uploadInBackground=false >> 
~/.gradle/gradle.properties
+
     - name: Setup Gradle with Develocity
       if: ${{ inputs.develocity-access-key != '' && inputs.build-scan-publish 
== 'true' }}
       uses: 
gradle/actions/setup-gradle@39e147cb9de83bb9910b8ef8bd7fff0ee20fcd6f
       with:
         develocity-injection-enabled: true
         develocity-url: https://develocity.apache.org
+        # expected format is develocity.apache.org:<access-key>
         develocity-access-key: ${{ inputs.develocity-access-key }}
+        build-scan-publish: ${{ inputs.build-scan-publish }}
         cache-read-only: ${{ inputs.cache-read-only }}
         add-job-summary: ${{ inputs.add-job-summary }}
 
diff --git a/.github/actions/tune-runner-vm/action.yml 
b/.github/actions/tune-runner-vm/action.yml
index d071f534dbb..00eb084990e 100644
--- a/.github/actions/tune-runner-vm/action.yml
+++ b/.github/actions/tune-runner-vm/action.yml
@@ -65,8 +65,8 @@ runs:
 
             # stop unnecessary services
             sudo systemctl stop php8.3-fpm.service ModemManager.service \
-              multipathd.service udisks2.service walinuxagent.service || true
-          
+              multipathd.socket multipathd.service udisks2.service 
walinuxagent.service || true
+
             echo '::endgroup::'
 
             # show memory
diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml
index 7f6c5480f2d..bf029bde911 100644
--- a/.github/workflows/codeql.yaml
+++ b/.github/workflows/codeql.yaml
@@ -35,8 +35,8 @@ env:
 
 jobs:
   analyze:
-    # only run scheduled analysis in apache/pulsar repository
-    if: ${{ (github.event_name == 'schedule' && github.repository == 
'apache/pulsar') || github.event_name != 'schedule' }}
+    # only run on push and schedule in apache/pulsar repo
+    if: ${{ github.repository == 'apache/pulsar' || github.event_name == 
'workflow_dispatch' }}
     name: Analyze
     runs-on: 'ubuntu-latest'
     timeout-minutes: 360
diff --git a/.github/workflows/pulsar-ci-flaky.yaml 
b/.github/workflows/pulsar-ci-flaky.yaml
index 041970735a9..a7ce06573e1 100644
--- a/.github/workflows/pulsar-ci-flaky.yaml
+++ b/.github/workflows/pulsar-ci-flaky.yaml
@@ -27,7 +27,7 @@ on:
   schedule:
     # scheduled job with JDK 21
     - cron: '0 12 * * *'
-    # scheduled job with JDK 17
+    # scheduled job with JDK 25
     # if cron expression is changed, make sure to update the expression in 
jdk_major_version step in preconditions job
     - cron: '0 6 * * *'
   workflow_dispatch:
@@ -42,9 +42,7 @@ on:
         required: true
         type: choice
         options:
-          - '17'
           - '21'
-          - '24'
           - '25'
         default: '21'
       trace_test_resource_cleanup:
@@ -106,9 +104,9 @@ jobs:
       - name: Select JDK major version
         id: jdk_major_version
         run: |
-          # use JDK 17 for the scheduled build with cron expression '0 6 * * *'
+          # use JDK 25 for the scheduled build with cron expression '0 6 * * *'
           if [[ "${{ github.event_name == 'schedule' && github.event.schedule 
== '0 6 * * *' && 'true' || 'false' }}" == "true" ]]; then
-            echo "jdk_major_version=17" >> $GITHUB_OUTPUT
+            echo "jdk_major_version=25" >> $GITHUB_OUTPUT
             exit 0
           fi
           # use JDK 21 for build unless overridden with workflow_dispatch input
diff --git a/.github/workflows/pulsar-ci.yaml b/.github/workflows/pulsar-ci.yaml
index 35d7ab41318..4cfdfdf6e5c 100644
--- a/.github/workflows/pulsar-ci.yaml
+++ b/.github/workflows/pulsar-ci.yaml
@@ -27,7 +27,7 @@ on:
   schedule:
     # scheduled job with JDK 21
     - cron: '0 12 * * *'
-    # scheduled job with JDK 17
+    # scheduled job with JDK 25
     # if cron expression is changed, make sure to update the expression in 
jdk_major_version step in preconditions job
     - cron: '0 6 * * *'
   workflow_dispatch:
@@ -37,9 +37,7 @@ on:
         required: true
         type: choice
         options:
-          - '17'
           - '21'
-          - '24'
           - '25'
         default: '21'
       trace_test_resource_cleanup:
@@ -99,9 +97,9 @@ jobs:
       - name: Select JDK major version
         id: jdk_major_version
         run: |
-          # use JDK 17 for the scheduled build with cron expression '0 6 * * *'
+          # use JDK 25 for the scheduled build with cron expression '0 6 * * *'
           if [[ "${{ github.event_name == 'schedule' && github.event.schedule 
== '0 6 * * *' && 'true' || 'false' }}" == "true" ]]; then
-            echo "jdk_major_version=17" >> $GITHUB_OUTPUT
+            echo "jdk_major_version=25" >> $GITHUB_OUTPUT
             exit 0
           fi
           # use JDK 21 for build unless overridden with workflow_dispatch input
@@ -443,7 +441,7 @@ jobs:
         run: tar xf gradle-build-outputs.tar
 
       - name: Build java-test-image Docker image
-        run: ./gradlew :tests:java-test-image:dockerBuild
+        run: ./gradlew :tests:java-test-image:dockerBuild${{ 
env.CI_JDK_MAJOR_VERSION != '21' && format(' -PdockerJavaVersion={0}', 
env.CI_JDK_MAJOR_VERSION) || '' }}
 
       - name: Save docker image to file
         run: |
@@ -500,10 +498,10 @@ jobs:
             upload_name: SHADE_RUN_21
             runtime_jdk: 21
 
-          - name: Shade on Java 24
+          - name: Shade on Java 25
             group: SHADE_RUN
-            upload_name: SHADE_RUN_24
-            runtime_jdk: 24
+            upload_name: SHADE_RUN_25
+            runtime_jdk: 25
 
           - name: Standalone
             group: STANDALONE
@@ -534,11 +532,13 @@ jobs:
         with:
           limit-access-to-actor: true
 
-      - name: Set up JDK ${{ env.CI_JDK_MAJOR_VERSION }}
+      - name: Set up JDK ${{ env.CI_JDK_MAJOR_VERSION }}${{ matrix.runtime_jdk 
&& matrix.runtime_jdk != env.CI_JDK_MAJOR_VERSION && format(' and {0}', 
matrix.runtime_jdk) || '' }}
         uses: actions/setup-java@v5
         with:
           distribution: ${{ env.JDK_DISTRIBUTION }}
-          java-version: ${{ env.CI_JDK_MAJOR_VERSION }}
+          java-version: |
+            ${{ matrix.runtime_jdk != env.CI_JDK_MAJOR_VERSION && 
matrix.runtime_jdk || '' }}
+            ${{ env.CI_JDK_MAJOR_VERSION }}
 
       - name: Setup Gradle
         uses: ./.github/actions/setup-gradle
@@ -567,16 +567,9 @@ jobs:
         run: |
           ${{ matrix.setup }}
 
-      - name: Set up runtime JDK ${{ matrix.runtime_jdk }}
-        uses: actions/setup-java@v5
-        if: ${{ matrix.runtime_jdk }}
-        with:
-          distribution: ${{ env.JDK_DISTRIBUTION }}
-          java-version: ${{ matrix.runtime_jdk }}
-
       - name: Run integration test group '${{ matrix.group }}'
         run: |
-          ./pulsar-build/run_integration_group_gradle.sh ${{ matrix.group }}
+          ./pulsar-build/run_integration_group_gradle.sh ${{ matrix.group 
}}${{ matrix.runtime_jdk && matrix.runtime_jdk != env.CI_JDK_MAJOR_VERSION && 
format(' -PtestJavaVersion={0}', matrix.runtime_jdk) || '' }}
 
       - name: print JVM thread dumps when cancelled
         if: cancelled()
@@ -666,7 +659,7 @@ jobs:
         run: tar xf gradle-build-outputs.tar
 
       - name: Build pulsar-test-latest-version Docker image
-        run: ./gradlew :tests:latest-version-image:dockerBuild
+        run: ./gradlew :tests:latest-version-image:dockerBuild${{ 
env.CI_JDK_MAJOR_VERSION != '21' && format(' -PdockerJavaVersion={0}', 
env.CI_JDK_MAJOR_VERSION) || '' }}
 
       - name: Check binary licenses
         run: |
diff --git a/README.md b/README.md
index f4600d5e186..bca501cfd99 100644
--- a/README.md
+++ b/README.md
@@ -168,7 +168,8 @@ Docker image Java runtime: 17
 
     | Pulsar Version   |                                   JDK Version         
                           |
     
|------------------|:--------------------------------------------------------------------------------:|
-    | master and 4.0+  | [JDK 
21](https://adoptium.net/en-GB/temurin/releases?version=21&os=any&arch=any) | 
+    | master           | [JDK 
21](https://adoptium.net/en-GB/temurin/releases?version=21&os=any&arch=any) or 
[JDK 
25](https://adoptium.net/en-GB/temurin/releases?version=25&os=any&arch=any) |
+    | 4.0+             | [JDK 
21](https://adoptium.net/en-GB/temurin/releases?version=21&os=any&arch=any) |
     | 2.11 +           | [JDK 
17](https://adoptium.net/en-GB/temurin/releases?version=17&os=any&arch=any) |
     | 2.8 / 2.9 / 2.10 | [JDK 
11](https://adoptium.net/en-GB/temurin/releases?version=11&os=any&arch=any) |
     | 2.7 -            |  [JDK 
8](https://adoptium.net/en-GB/temurin/releases?version=8&os=any&arch=any)  |
@@ -181,6 +182,8 @@ There is also a guide for [setting up the tooling for 
building Pulsar](https://p
 >
 > This project includes a [Gradle 
 > Wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html) so 
 > no separate Gradle installation is needed.
 > Use `./gradlew` on Linux/macOS and `gradlew.bat` on Windows.
+>
+> For a better developer experience, install [Gradle command-line 
completion](https://docs.gradle.org/current/userguide/command_line_interface.html#sec:command_line_completion)
 ([gradle-completion 
installation](https://github.com/gradle/gradle-completion?tab=readme-ov-file#gradle-completion))
 for bash and zsh shells.
 
 ### Build
 
diff --git 
a/build-logic/conventions/src/main/kotlin/pulsar.java-conventions.gradle.kts 
b/build-logic/conventions/src/main/kotlin/pulsar.java-conventions.gradle.kts
index 666648175b5..daf10c90098 100644
--- a/build-logic/conventions/src/main/kotlin/pulsar.java-conventions.gradle.kts
+++ b/build-logic/conventions/src/main/kotlin/pulsar.java-conventions.gradle.kts
@@ -116,7 +116,16 @@ dependencies {
     "testImplementation"(catalog.findLibrary("slf4j-api").get())
 }
 
+// Allow overriding the JDK used for running tests via -PtestJavaVersion=17
+val testJavaVersion = providers.gradleProperty("testJavaVersion").map { 
it.toInt() }
+val javaToolchains = extensions.getByType<JavaToolchainService>()
+
 tasks.withType<Test>().configureEach {
+    testJavaVersion.orNull?.let { version ->
+        javaLauncher.set(javaToolchains.launcherFor {
+            languageVersion.set(JavaLanguageVersion.of(version))
+        })
+    }
     useTestNG {
         listeners.addAll(listOf(
             "org.apache.pulsar.tests.PulsarTestListener",
diff --git a/distribution/server/src/assemble/LICENSE.bin.txt 
b/distribution/server/src/assemble/LICENSE.bin.txt
index 5177464d39e..a5f26bc9ffe 100644
--- a/distribution/server/src/assemble/LICENSE.bin.txt
+++ b/distribution/server/src/assemble/LICENSE.bin.txt
@@ -289,7 +289,7 @@ The Apache Software License, Version 2.0
     - org.apache.commons-commons-collections4-4.5.0.jar
     - org.apache.commons-commons-compress-1.28.0.jar
     - org.apache.commons-commons-configuration2-2.12.0.jar
-    - org.apache.commons-commons-lang3-3.19.0.jar
+    - org.apache.commons-commons-lang3-3.20.0.jar
     - org.apache.commons-commons-text-1.14.0.jar
  * Netty
     - io.netty-netty-buffer-4.1.132.Final.jar
diff --git a/distribution/shell/src/assemble/LICENSE.bin.txt 
b/distribution/shell/src/assemble/LICENSE.bin.txt
index 70cc3a61fa3..7d42b4b9467 100644
--- a/distribution/shell/src/assemble/LICENSE.bin.txt
+++ b/distribution/shell/src/assemble/LICENSE.bin.txt
@@ -341,7 +341,7 @@ The Apache Software License, Version 2.0
  * Apache Commons
     - commons-codec-1.20.0.jar
     - commons-io-2.21.0.jar
-    - commons-lang3-3.19.0.jar
+    - commons-lang3-3.20.0.jar
     - commons-text-1.14.0.jar
     - commons-compress-1.28.0.jar
  * Netty
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 4ed2e758314..d75fa3a73b7 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -19,7 +19,6 @@
 [versions]
 # Core
 pulsar = "4.3.0-SNAPSHOT"
-java = "17"
 # Docker
 docker-jdk = "21"
 pulsar-client-python = "3.10.0"
@@ -45,7 +44,7 @@ opentelemetry-instrumentation = "2.21.0"
 opentelemetry-instrumentation-alpha = "2.21.0-alpha"
 opentelemetry-semconv = "1.37.0"
 # Apache Commons
-commons-lang3 = "3.19.0"
+commons-lang3 = "3.20.0"
 commons-io = "2.21.0"
 commons-codec = "1.20.0"
 commons-compress = "1.28.0"
diff --git a/microbench/README.md b/microbench/README.md
index 9138c3de2c1..6b1b8af593d 100644
--- a/microbench/README.md
+++ b/microbench/README.md
@@ -32,7 +32,7 @@ The benchmarks are written using 
[JMH](http://openjdk.java.net/projects/code-too
 ./gradlew :microbench:shadowJar
 
 # run the benchmarks using the standalone shaded jar in any environment
-java -jar microbench/build/libs/microbenchmarks.jar
+java -jar microbench/build/libs/microbench-*-benchmarks.jar
 ```
 
 ### Running specific benchmarks
@@ -40,25 +40,25 @@ java -jar microbench/build/libs/microbenchmarks.jar
 Display help:
 
 ```shell
-java -jar microbench/build/libs/microbenchmarks.jar -h
+java -jar microbench/build/libs/microbench-*-benchmarks.jar -h
 ```
 
 Listing all benchmarks:
 
 ```shell
-java -jar microbench/build/libs/microbenchmarks.jar -l
+java -jar microbench/build/libs/microbench-*-benchmarks.jar -l
 ```
 
 Running specific benchmarks:
 
 ```shell
-java -jar microbench/build/libs/microbenchmarks.jar ".*BenchmarkName.*"
+java -jar microbench/build/libs/microbench-*-benchmarks.jar ".*BenchmarkName.*"
 ```
 
 Running specific benchmarks with machine-readable output and saving the output 
to a file:
 
 ```shell
-java -jar microbench/build/libs/microbenchmarks.jar -rf json -rff 
jmh-result-$(date +%s).json ".*BenchmarkName.*" | tee jmh-result-$(date +%s).txt
+java -jar microbench/build/libs/microbench-*-benchmarks.jar -rf json -rff 
jmh-result-$(date +%s).json ".*BenchmarkName.*" | tee jmh-result-$(date +%s).txt
 ```
 
 The `jmh-result-*.json` file can be used to visualize the results using [JMH 
Visualizer](https://jmh.morethan.io/).
@@ -66,16 +66,37 @@ The `jmh-result-*.json` file can be used to visualize the 
results using [JMH Vis
 Checking what benchmarks match the pattern:
 
 ```shell
-java -jar microbench/build/libs/microbenchmarks.jar ".*BenchmarkName.*" -lp
+java -jar microbench/build/libs/microbench-*-benchmarks.jar 
".*BenchmarkName.*" -lp
 ```
 
 Profiling benchmarks with 
[async-profiler](https://github.com/async-profiler/async-profiler):
 
+Set `LIBASYNCPROFILER_PATH` to the path of the async-profiler library.
+
+Corretto JDK ships with async-profiler (asprof binary and libasyncProfiler 
dynamic library)
+
+```shell
+LIBASYNCPROFILER_PATH=$(ls $JAVA_HOME/lib/libasyncProfiler.*)
+```
+
+Alternatively, download async-profiler from 
https://github.com/async-profiler/async-profiler/releases and install to 
~/async-profiler directory.
+
+Mac OS example:
+
 ```shell
-# example of profiling with async-profiler
-# download async-profiler from 
https://github.com/async-profiler/async-profiler/releases
 LIBASYNCPROFILER_PATH=$HOME/async-profiler/lib/libasyncProfiler.dylib
-java -jar microbench/build/libs/microbenchmarks.jar -prof 
async:libPath=$LIBASYNCPROFILER_PATH\;output=flamegraph\;dir=profile-results 
".*BenchmarkName.*"
+```
+
+Linux example:
+
+```shell
+# macos example
+LIBASYNCPROFILER_PATH=$HOME/async-profiler/lib/libasyncProfiler.dylib
+```
+
+Then run the benchmarks with the `-prof` argument:
+```shell
+java -jar microbench/build/libs/microbench-*-benchmarks.jar -prof 
async:libPath=$LIBASYNCPROFILER_PATH\;output=flamegraph\;dir=profile-results 
".*BenchmarkName.*"
 ```
 
 When profiling on Mac OS, you might need to add `\;event=itimer` to the 
`-prof` argument since it's the only [async profiler CPU sampling engine that 
supports Mac 
OS](https://github.com/async-profiler/async-profiler/blob/master/docs/CpuSamplingEngines.md#summary).
 The default value for `event` is `cpu`.
@@ -83,5 +104,5 @@ When profiling on Mac OS, you might need to add 
`\;event=itimer` to the `-prof`
 It's possible to add options to the async-profiler that aren't supported by 
the JMH async-profiler plugin. This can be done by adding `rawCommand` option 
to the `-prof` argument. This example shows how to add `all` (new in Async 
Profiler 4.1), `jfrsync` (record JFR events such as garbage collection) and 
`cstack=vmx` options.
 
 ```shell
-java -jar microbench/build/libs/microbenchmarks.jar -prof 
async:libPath=$LIBASYNCPROFILER_PATH\;output=jfr\;dir=profile-results\;rawCommand=all,jfrsync,cstack=vmx
 ".*BenchmarkName.*"
+java -jar microbench/build/libs/microbench-*-benchmarks.jar -prof 
async:libPath=$LIBASYNCPROFILER_PATH\;output=jfr\;dir=profile-results\;rawCommand=all,jfrsync,cstack=vmx
 ".*BenchmarkName.*"
 ```
\ No newline at end of file
diff --git a/pulsar-build/run_integration_group_gradle.sh 
b/pulsar-build/run_integration_group_gradle.sh
index 388aa16dcfe..8ab24b08936 100755
--- a/pulsar-build/run_integration_group_gradle.sh
+++ b/pulsar-build/run_integration_group_gradle.sh
@@ -140,7 +140,8 @@ test_group_shade_run() {
   ./gradlew --no-configuration-cache \
     :tests:pulsar-client-shade-test:test \
     :tests:pulsar-client-admin-shade-test:test \
-    :tests:pulsar-client-all-shade-test:test
+    :tests:pulsar-client-all-shade-test:test \
+    "$@"
   echo "::endgroup::"
   "$SCRIPT_DIR/pulsar_ci_tool.sh" move_test_reports
 }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 45ba3587e7f..45847e44465 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -36,10 +36,27 @@ dependencyResolutionManagement {
             }
         }
     }
+
+    // override docker-jdk version with -PdockerJavaVersion=21|25
+    val overrideDockerJavaVersion = 
settings.providers.gradleProperty("dockerJavaVersion")
+    if (overrideDockerJavaVersion.isPresent) {
+        versionCatalogs {
+            create("libs") {
+                version("docker-jdk", overrideDockerJavaVersion.get())
+            }
+        }
+    }
 }
 
 rootProject.name = "pulsar"
 
+// Running this build requires Java 21 or 25. Version check can be skipped 
with -PskipJavaVersionCheck parameter.
+val javaVersion = providers.provider { JavaVersion.current() }
+val statisfiedJavaVersion = javaVersion.map { it == JavaVersion.VERSION_21 || 
it == JavaVersion.VERSION_25 }
+require(providers.gradleProperty("skipJavaVersionCheck").isPresent || 
statisfiedJavaVersion.get()) {
+    "This build requires Java 21 or 25, but is running on Java 
${javaVersion.get()}. Pass -PskipJavaVersionCheck to skip this check."
+}
+
 // 
──────────────────────────────────────────────────────────────────────────────
 // Core modules
 // 
──────────────────────────────────────────────────────────────────────────────
diff --git 
a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/offload/TestFileSystemOffload.java
 
b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/offload/TestFileSystemOffload.java
index d658d80ddf4..9f5653ef779 100644
--- 
a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/offload/TestFileSystemOffload.java
+++ 
b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/offload/TestFileSystemOffload.java
@@ -22,11 +22,22 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.function.Supplier;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.JavaVersion;
+import org.apache.commons.lang3.SystemUtils;
+import org.testng.SkipException;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 @Slf4j
 public class TestFileSystemOffload extends TestBaseOffload {
 
+    @BeforeClass
+    public void checkJavaVersion() {
+        if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_25)) {
+            throw new SkipException("Filesystem Offload is incompatible with 
Java 25");
+        }
+    }
+
     @Test(dataProvider =  "ServiceAndAdminUrls")
     public void testPublishOffloadAndConsumeViaCLI(Supplier<String> 
serviceUrl, Supplier<String> adminUrl)
             throws Exception {
diff --git 
a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/offload/TestOffloadDeletionFS.java
 
b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/offload/TestOffloadDeletionFS.java
index 70c2527e8ad..873b73d8c2b 100644
--- 
a/tests/integration/src/test/java/org/apache/pulsar/tests/integration/offload/TestOffloadDeletionFS.java
+++ 
b/tests/integration/src/test/java/org/apache/pulsar/tests/integration/offload/TestOffloadDeletionFS.java
@@ -25,14 +25,25 @@ import java.util.List;
 import java.util.Map;
 import java.util.function.Supplier;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.JavaVersion;
+import org.apache.commons.lang3.SystemUtils;
 import org.apache.pulsar.common.naming.TopicName;
 import org.apache.pulsar.tests.integration.docker.ContainerExecException;
 import org.apache.pulsar.tests.integration.docker.ContainerExecResult;
+import org.testng.SkipException;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 @Slf4j
 public class TestOffloadDeletionFS extends TestBaseOffload {
 
+    @BeforeClass
+    public void checkJavaVersion() {
+        if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_25)) {
+            throw new SkipException("Filesystem Offload is incompatible with 
Java 25");
+        }
+    }
+
     @Override
     protected int getEntrySize() {
         return 512;
diff --git a/tiered-storage/file-system/build.gradle.kts 
b/tiered-storage/file-system/build.gradle.kts
index ab9366d1573..83558e1ae4d 100644
--- a/tiered-storage/file-system/build.gradle.kts
+++ b/tiered-storage/file-system/build.gradle.kts
@@ -81,3 +81,12 @@ dependencies {
     testImplementation("org.eclipse.jetty:jetty-servlet:9.4.58.v20250814")
     testImplementation("org.eclipse.jetty:jetty-util:9.4.58.v20250814")
 }
+
+// Hadoop 3.4.x is incompatible with Java 25
+val testJavaVersion = providers.gradleProperty("testJavaVersion")
+val effectiveTestJava = testJavaVersion
+    .orElse(provider { JavaVersion.current().majorVersion })
+    .map { it.toInt() }
+tasks.withType<Test>().configureEach {
+    enabled = effectiveTestJava.get() < 25
+}
\ No newline at end of file

Reply via email to