You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by mg...@apache.org on 2026/03/09 15:33:03 UTC

(kudu) 01/02: [build][java] KUDU-3681: Move to JDK17

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

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

commit 9b8aef91451cbbf646a59232eec8f3acbb7016a4
Author: zchovan <zc...@cloudera.com>
AuthorDate: Mon Aug 4 15:24:11 2025 +0200

    [build][java] KUDU-3681: Move to JDK17
    
    This change aims to support building and running the java bits with Java 17.
    
    The changes:
    * natively set release version when building with Java 9+
    * use 'org.apache.tomcat:annotations-api' as a compileOnly dependency for the
      'javax.annotations' package, since that was deprecated in Java 9
    * upgrade jacoco dependency from 0.8.6 to 0.8.8 for Java17 compatibility
    * upgrade the Hive dependency to 4.1.0
    * after this change, it is no longer possible to compile Kudu java bits
      with JDK versions lower than 17
    
    Change-Id: I1f73c0d3a79e1c37b12a173881273b4b68d718b3
    Reviewed-on: http://gerrit.cloudera.org:8080/23259
    Reviewed-by: Marton Greber <gr...@gmail.com>
    Tested-by: Marton Greber <gr...@gmail.com>
    Reviewed-by: Gabriella Lotz <lo...@gmail.com>
---
 java/config/spotbugs/excludeFilter.xml             |  2 ++
 java/gradle.properties                             | 11 ---------
 java/gradle/compile.gradle                         |  9 ++++----
 java/gradle/dependencies.gradle                    | 11 +++++----
 java/gradle/docs.gradle                            |  2 +-
 java/gradle/quality.gradle                         | 27 ++++++++++++++--------
 java/gradle/tests.gradle                           |  8 +++++--
 java/kudu-backup-common/build.gradle               |  2 ++
 java/kudu-hive/build.gradle                        |  1 +
 .../hive/metastore/TestKuduMetastorePlugin.java    | 24 +++++++++----------
 java/kudu-proto/build.gradle                       |  1 +
 .../apache/kudu/spark/kudu/DefaultSourceTest.scala |  8 +++----
 src/kudu/ranger-kms/mini_ranger_kms.cc             | 16 +++++++++++++
 src/kudu/ranger/mini_ranger.cc                     | 12 ++++++++++
 14 files changed, 86 insertions(+), 48 deletions(-)

diff --git a/java/config/spotbugs/excludeFilter.xml b/java/config/spotbugs/excludeFilter.xml
index 005fbea26..a33205b82 100644
--- a/java/config/spotbugs/excludeFilter.xml
+++ b/java/config/spotbugs/excludeFilter.xml
@@ -130,6 +130,8 @@
             <Bug pattern="RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT" />
             <!-- Tests often cast to a known class. -->
             <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+            <!-- In tests, instanceof assertions are often intentional for readability. -->
+            <Bug pattern="JUA_DONT_ASSERT_INSTANCEOF_IN_TESTS" />
             <!-- Tests don't need to worry about serialization compatibility. -->
             <Bug pattern="SE_NO_SERIALVERSIONID" />
             <!-- Often tests testing exceptions have "exception" in the name. -->
diff --git a/java/gradle.properties b/java/gradle.properties
index b490f9fb5..f9271dddf 100755
--- a/java/gradle.properties
+++ b/java/gradle.properties
@@ -25,17 +25,6 @@ url = https://kudu.apache.org/
 # The Maven respository used when uploading artifacts.
 mavenPublishUrl = https://repository.apache.org/service/local/staging/deploy/maven2
 
-# Version passed to javac's -source, -target, and --release (Java 9+) parameters:
-#
-# -source Specifies the version of source code accepted.
-# -target Generate class files that target a specified version of the VM. Class
-#         files will run on the specified target and on later versions, but not
-#         on earlier versions of the VM.
-# --release (Java 9+) Configures the compiler to produce class files that will
-#                     link against an implementation of the given platform version.
-#                     (See http://openjdk.java.net/jeps/247)
-javaCompatibility = 8
-
 encoding = UTF-8
 # Used by the scalafmt plugin because the encoding property can't be passed.
 systemProp.file.encoding = UTF-8
diff --git a/java/gradle/compile.gradle b/java/gradle/compile.gradle
index 896b24a40..5ea7d36fe 100644
--- a/java/gradle/compile.gradle
+++ b/java/gradle/compile.gradle
@@ -19,15 +19,14 @@
 
 // JVM based compiler configuration
 tasks.withType(AbstractCompile) {
-  sourceCompatibility = "1.$javaCompatibility"
-  targetCompatibility = "1.$javaCompatibility"
   // --release is the recommended way to select the target release, but it's only supported in
   // Java 9+ so we also set -source and -target via `sourceCompatibility` and `targetCompatibility`.
-  // If/when Gradle supports `--release` natively (https://github.com/gradle/gradle/issues/2510),
-  // we should switch to that.
+  // The release option takes precedence over source/targetCompatibility:
+  // https://docs.gradle.org/7.6.4/dsl/org.gradle.api.tasks.compile.CompileOptions.html#org.gradle.api.tasks.compile.CompileOptions:release
   if (JavaVersion.current().isJava9Compatible()) {
-    options.compilerArgs << "--release" << "$javaCompatibility"
+    options.release = 17
   }
+
   options.encoding = encoding // make sure the encoding is defined by the project and not the system default.
   options.incremental = true // enable incremental compilation.
   options.compilerArgs << '-proc:none' // Ignore leaked annotation processors on the compile classpath.
diff --git a/java/gradle/dependencies.gradle b/java/gradle/dependencies.gradle
index baa15239f..2118e2f6f 100755
--- a/java/gradle/dependencies.gradle
+++ b/java/gradle/dependencies.gradle
@@ -25,6 +25,7 @@ ext {
 }
 
 versions += [
+    annotations         : "6.0.53",
     assertj             : "3.27.3",
     async               : "1.4.1",
     checkstyle          : "8.36.1",
@@ -41,9 +42,9 @@ versions += [
     hadoop              : "3.4.1",
     hamcrest            : "3.0",
     hdrhistogram        : "2.2.2",
-    hive                : "3.1.2",
+    hive                : "4.1.0",
     httpClient          : "4.5.14",
-    jacoco              : "0.8.6",
+    jacoco              : "0.8.8",
     jepsen              : "0.1.5",
     jetty               : "9.4.57.v20241219",
     jmh                 : "1.37",
@@ -65,7 +66,7 @@ versions += [
     slf4j               : "1.7.36",
     spark2              : "2.4.8",
     spark               : "3.5.5",
-    spotBugs            : "4.1.1",
+    spotBugs            : "4.2.2",
     yetus               : "0.13.0"
 ]
 
@@ -89,6 +90,7 @@ versions["sparkBase"] = versions.spark.substring(0, versions.spark.indexOf("."))
 
 libs += [
     assertj              : "org.assertj:assertj-core:$versions.assertj",
+    annotations          : "org.apache.tomcat:annotations-api:$versions.annotations",
     async                : "com.stumbleupon:async:$versions.async",
     clojure              : "org.clojure:clojure:$versions.clojure",
     clojureToolsCli      : "org.clojure:tools.cli:$versions.clojureToolsCli",
@@ -108,12 +110,13 @@ libs += [
     guava                : "com.google.guava:guava:$versions.guava",
     hadoopClient         : "org.apache.hadoop:hadoop-client:$versions.hadoop",
     hadoopCommon         : "org.apache.hadoop:hadoop-common:$versions.hadoop",
+    hadoopHdfs           : "org.apache.hadoop:hadoop-hdfs:$versions.hadoop",
     hadoopMiniCluster    : "org.apache.hadoop:hadoop-minicluster:$versions.hadoop",
     hadoopMRClientCommon : "org.apache.hadoop:hadoop-mapreduce-client-common:$versions.hadoop",
     hadoopMRClientCore   : "org.apache.hadoop:hadoop-mapreduce-client-core:$versions.hadoop",
     hamcrest             : "org.hamcrest:hamcrest:$versions.hamcrest",
     hdrhistogram         : "org.hdrhistogram:HdrHistogram:$versions.hdrhistogram",
-    hiveMetastore        : "org.apache.hive:hive-metastore:$versions.hive",
+    hiveMetastore        : "org.apache.hive:hive-standalone-metastore-server:$versions.hive",
     hiveTestUtils        : "org.apache.hive:hive-testutils:$versions.hive",
     httpClient           : "org.apache.httpcomponents:httpclient:$versions.httpClient",
     httpMime             : "org.apache.httpcomponents:httpmime:$versions.httpClient",
diff --git a/java/gradle/docs.gradle b/java/gradle/docs.gradle
index d9ef7cf08..851b724a4 100644
--- a/java/gradle/docs.gradle
+++ b/java/gradle/docs.gradle
@@ -38,7 +38,7 @@ afterEvaluate {
     options.docletpath = configurations.doclet.files.asType(List)
     options.doclet = "org.apache.yetus.audience.tools.IncludePublicAnnotationsStandardDoclet"
     // Link to Java SE javadoc
-    options.links "https://docs.oracle.com/javase/$javaCompatibility/docs/api/"
+    options.links "https://docs.oracle.com/javase/17/docs/api/"
     // Hacky workaround to YETUS-627. Replace with options.noTimestamp
     // once this workaround is no longer needed.
     options.addStringOption("notimestamp", "org.apache.kudu")
diff --git a/java/gradle/quality.gradle b/java/gradle/quality.gradle
index ffc10f3b9..7b0391370 100644
--- a/java/gradle/quality.gradle
+++ b/java/gradle/quality.gradle
@@ -135,16 +135,25 @@ if (project.name != "kudu-proto" && project.name != "kudu-jepsen" &&
   }
 }
 
-// Define a Java API signature for use by animal-sniffer. It'll enforce that all
-// Java API usage adheres to this signature.
-dependencies {
-  signature "org.codehaus.mojo.signature:java1$javaCompatibility:1.0@signature"
-}
+def enableAnimalSniffer = !JavaVersion.current().isJava11Compatible()
+if (enableAnimalSniffer) {
+  // Define a Java API signature for use by animal-sniffer. It'll enforce that all
+  // Java API usage adheres to this signature.
+  dependencies {
+    signature "org.codehaus.mojo.signature:java18:1.0@signature"
+  }
 
-// Create an aggregate animal-sniffer task.
-// This simplifies running animal-sniffer on all the code by only needing one task instead of multiple in your command.
-task animalsniffer(dependsOn: [animalsnifferMain, animalsnifferTest], group: "Verification") {
-  description = "Run animal-sniffer analysis."
+  // Create an aggregate animal-sniffer task.
+  // This simplifies running animal-sniffer on all the code by only needing one task instead of multiple in your command.
+  task animalsniffer(dependsOn: [animalsnifferMain, animalsnifferTest], group: "Verification") {
+    description = "Run animal-sniffer analysis."
+  }
+} else {
+  // Animal Scents signatures are only available up to Java 8.
+  // On JDK 11+, animalsniffer reports false positives for valid JDK APIs.
+  tasks.matching { task -> task.name.startsWith("animalsniffer") }.configureEach {
+    enabled = false
+  }
 }
 
 // Configure the versions plugin to only show dependency updates for released versions.
diff --git a/java/gradle/tests.gradle b/java/gradle/tests.gradle
index 4157c559b..0946c97b3 100644
--- a/java/gradle/tests.gradle
+++ b/java/gradle/tests.gradle
@@ -46,16 +46,20 @@ tasks.withType(Test) {
   jvmArgs += "-Xmx1g"
   // Hide the Java 9+ warnings about illegal reflective access
   if (JavaVersion.current().isJava9Compatible()) {
-    jvmArgs += "--illegal-access=permit"
     def reflectionModules = [
         "java.base/java.io",   // java.io reflection is used by Spark
         "java.base/java.lang", // java.lang reflection is used by TestUtils
+        "java.base/java.lang.reflect", // java.io reflection is used by Spark
         "java.base/java.net",  // java.net reflection is used by FakeDNS
         "java.base/java.nio",  // java.nio reflection is used by Protobuf
         "java.base/java.util", // java.util reflection is used by Spark
         "java.base/java.util.concurrent", // java.util.concurrent reflection is used by Spark
+        "java.base/java.util.concurrent.atomic", // java.io reflection is used by Spark
         "java.base/sun.nio.ch", // sun.nio.ch reflection is used by Netty
-        "java.security.jgss/sun.security.krb5" // sun.security.krb5 reflection is used by Hadoop's KerberosUtil
+        "java.base/sun.nio.cs", // java.io reflection is used by Spark
+        "java.base/sun.security.action", // java.io reflection is used by Spark
+        "java.base/sun.util.calendar", // java.io reflection is used by Spark
+        "java.security.jgss/sun.security.krb5", // sun.security.krb5 reflection is used by Hadoop's KerberosUtil
     ]
     reflectionModules.forEach { module ->
       jvmArgs += "--add-opens=$module=ALL-UNNAMED"
diff --git a/java/kudu-backup-common/build.gradle b/java/kudu-backup-common/build.gradle
index 6a6292315..1fd36ef4d 100644
--- a/java/kudu-backup-common/build.gradle
+++ b/java/kudu-backup-common/build.gradle
@@ -32,6 +32,8 @@ dependencies {
 
   implementation libs.yetusAnnotations
 
+  compileOnly libs.annotations
+
   testImplementation project(path: ":kudu-test-utils", configuration: "shadow")
   testImplementation libs.junit
   testImplementation libs.log4jApi
diff --git a/java/kudu-hive/build.gradle b/java/kudu-hive/build.gradle
index d86fe8841..462a80e70 100644
--- a/java/kudu-hive/build.gradle
+++ b/java/kudu-hive/build.gradle
@@ -37,6 +37,7 @@ dependencies {
 
   testImplementation libs.hiveMetastore
   testImplementation libs.hadoopCommon
+  testImplementation libs.hadoopHdfs
   testImplementation libs.hadoopMRClientCommon
 }
 
diff --git a/java/kudu-hive/src/test/java/org/apache/kudu/hive/metastore/TestKuduMetastorePlugin.java b/java/kudu-hive/src/test/java/org/apache/kudu/hive/metastore/TestKuduMetastorePlugin.java
index 589637adb..63d407b41 100644
--- a/java/kudu-hive/src/test/java/org/apache/kudu/hive/metastore/TestKuduMetastorePlugin.java
+++ b/java/kudu-hive/src/test/java/org/apache/kudu/hive/metastore/TestKuduMetastorePlugin.java
@@ -28,7 +28,6 @@ import java.util.UUID;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.hive.conf.HiveConf;
 import org.apache.hadoop.hive.metastore.DefaultPartitionExpressionProxy;
 import org.apache.hadoop.hive.metastore.HiveMetaStoreClient;
 import org.apache.hadoop.hive.metastore.MetaStoreEventListener;
@@ -41,7 +40,7 @@ import org.apache.hadoop.hive.metastore.api.StorageDescriptor;
 import org.apache.hadoop.hive.metastore.api.Table;
 import org.apache.hadoop.hive.metastore.api.hive_metastoreConstants;
 import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
-import org.apache.hadoop.hive.metastore.utils.MetaStoreUtils;
+import org.apache.hadoop.hive.metastore.utils.MetaStoreServerUtils;
 import org.apache.thrift.TException;
 import org.junit.After;
 import org.junit.Test;
@@ -53,7 +52,7 @@ import org.apache.kudu.test.cluster.MiniKuduCluster;
 public class TestKuduMetastorePlugin {
   private static final Logger LOG = LoggerFactory.getLogger(TestKuduMetastorePlugin.class);
 
-  private HiveConf clientConf;
+  private Configuration clientConf;
   private HiveMetaStoreClient client;
   private MiniKuduCluster miniCluster;
 
@@ -108,10 +107,11 @@ public class TestKuduMetastorePlugin {
     assertTrue(derbyLogFile.toFile().createNewFile());
     System.setProperty("derby.stream.error.file", derbyLogFile.toString());
 
-    int msPort = MetaStoreUtils.startMetaStore(hmsConf);
+    int msPort = MetaStoreServerUtils.startMetaStore(hmsConf);
 
-    clientConf = new HiveConf();
-    clientConf.setVar(HiveConf.ConfVars.METASTOREURIS, "thrift://localhost:" + msPort);
+    clientConf = MetastoreConf.newMetastoreConf(hmsConf);
+    clientConf.set(MetastoreConf.ConfVars.THRIFT_URIS.getVarname(),
+        "thrift://localhost:" + msPort);
 
     client = new HiveMetaStoreClient(clientConf);
 
@@ -327,7 +327,7 @@ public class TestKuduMetastorePlugin {
         // Also change the location to avoid MetastoreDefaultTransformer validation
         // that exists in some Hive versions.
         alteredTable.getSd().setLocation(String.format("%s/%s/%s",
-            clientConf.get(HiveConf.ConfVars.METASTOREWAREHOUSE.varname),
+            clientConf.get(MetastoreConf.ConfVars.WAREHOUSE.getVarname()),
             table.getDbName(), table.getTableName()));
         alteredTable.putToParameters(KuduMetastorePlugin.EXTERNAL_TABLE_KEY, "FALSE");
         alteredTable.putToParameters(KuduMetastorePlugin.EXTERNAL_PURGE_KEY, "FALSE");
@@ -341,7 +341,7 @@ public class TestKuduMetastorePlugin {
         // Also change the location to avoid MetastoreDefaultTransformer validation
         // that exists in some Hive versions.
         alteredTable.getSd().setLocation(String.format("%s/%s/%s",
-            clientConf.get(HiveConf.ConfVars.METASTOREWAREHOUSE.varname),
+            clientConf.get(MetastoreConf.ConfVars.WAREHOUSE.getVarname()),
             table.getDbName(), table.getTableName()));
         alteredTable.putToParameters(KuduMetastorePlugin.EXTERNAL_PURGE_KEY, "FALSE");
         client.alter_table(table.getDbName(), table.getTableName(), alteredTable);
@@ -357,7 +357,7 @@ public class TestKuduMetastorePlugin {
         // Also change the location to avoid MetastoreDefaultTransformer validation
         // that exists in some Hive versions.
         alteredTable.getSd().setLocation(String.format("%s/%s/%s",
-            clientConf.get(HiveConf.ConfVars.METASTOREWAREHOUSE.varname),
+            clientConf.get(MetastoreConf.ConfVars.WAREHOUSE.getVarname()),
             table.getDbName(), table.getTableName()));
         alteredTable.setTableType(TableType.EXTERNAL_TABLE.toString());
         alteredTable.putToParameters(KuduMetastorePlugin.EXTERNAL_TABLE_KEY, "TRUE");
@@ -376,7 +376,7 @@ public class TestKuduMetastorePlugin {
         // Also change the location to avoid MetastoreDefaultTransformer validation
         // that exists in some Hive versions.
         alteredTable.getSd().setLocation(String.format("%s/%s/%s",
-            clientConf.get(HiveConf.ConfVars.METASTOREWAREHOUSE.varname),
+            clientConf.get(MetastoreConf.ConfVars.WAREHOUSE.getVarname()),
             table.getDbName(), table.getTableName()));
         client.alter_table(table.getDbName(), table.getTableName(), alteredTable);
       }
@@ -391,7 +391,7 @@ public class TestKuduMetastorePlugin {
         // Also change the location to avoid MetastoreDefaultTransformer validation
         // that exists in some Hive versions.
         alteredTable.getSd().setLocation(String.format("%s/%s/%s",
-            clientConf.get(HiveConf.ConfVars.METASTOREWAREHOUSE.varname),
+            clientConf.get(MetastoreConf.ConfVars.WAREHOUSE.getVarname()),
             table.getDbName(), table.getTableName()));
         client.alter_table(table.getDbName(), table.getTableName(), alteredTable);
       }
@@ -401,7 +401,7 @@ public class TestKuduMetastorePlugin {
       // Also change the location to avoid MetastoreDefaultTransformer validation
       // that exists in some Hive versions.
       table.getSd().setLocation(String.format("%s/%s/%s",
-          clientConf.get(HiveConf.ConfVars.METASTOREWAREHOUSE.varname),
+          clientConf.get(MetastoreConf.ConfVars.WAREHOUSE.getVarname()),
           table.getDbName(), table.getTableName()));
       try {
         client.alter_table(table.getDbName(), table.getTableName(), table);
diff --git a/java/kudu-proto/build.gradle b/java/kudu-proto/build.gradle
index 98ee30346..2929eb37d 100644
--- a/java/kudu-proto/build.gradle
+++ b/java/kudu-proto/build.gradle
@@ -19,6 +19,7 @@ apply from: "$rootDir/gradle/protobuf.gradle"
 
 dependencies {
   implementation libs.protobufJava
+  compileOnly libs.annotations
 }
 
 // Add protobuf files to the proto source set.
diff --git a/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/DefaultSourceTest.scala b/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/DefaultSourceTest.scala
index 4dea01444..28a7ad43a 100644
--- a/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/DefaultSourceTest.scala
+++ b/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/DefaultSourceTest.scala
@@ -31,6 +31,7 @@ import org.apache.spark.sql.types.StructField
 import org.apache.spark.sql.types.StructType
 import org.junit.Assert._
 import org.apache.kudu.client.CreateTableOptions
+import org.apache.kudu.client.KuduException
 import org.apache.kudu.test.KuduTestHarness
 import org.apache.kudu.test.RandomUtils
 import org.apache.kudu.spark.kudu.SparkListenerUtil.withJobTaskCounter
@@ -966,7 +967,7 @@ class DefaultSourceTest extends KuduTestSuite with Matchers {
       val df = sqlContext.read.options(kuduOptions).format("kudu").load
       df.count()
     }
-    assertTrue(exception.getCause.getMessage.contains("this client is not authenticated"))
+    assertTrue(exception.getMessage.contains("this client is not authenticated"))
 
     KuduClientCache.clearCacheForTests()
     kuduOptions = Map(
@@ -992,7 +993,7 @@ class DefaultSourceTest extends KuduTestSuite with Matchers {
       df.count
     }
     assertTrue(
-      exception.getCause.getMessage
+      exception.getMessage
         .contains("client requires authentication, but server does not have Kerberos enabled"))
   }
 
@@ -1010,8 +1011,7 @@ class DefaultSourceTest extends KuduTestSuite with Matchers {
       val df = sqlContext.read.options(kuduOptions).format("kudu").load
       df.count
     }
-    assertTrue(
-      exception.getCause.getMessage.contains("server does not support required TLS encryption"))
+    assertTrue(exception.getMessage.contains("server does not support required TLS encryption"))
   }
 
   @Test
diff --git a/src/kudu/ranger-kms/mini_ranger_kms.cc b/src/kudu/ranger-kms/mini_ranger_kms.cc
index 5952a91eb..92ac6efe9 100644
--- a/src/kudu/ranger-kms/mini_ranger_kms.cc
+++ b/src/kudu/ranger-kms/mini_ranger_kms.cc
@@ -243,6 +243,22 @@ Status MiniRangerKMS::StartRangerKMS() {
     std::vector<string> args({
       JoinPathSegments(java_home_, "bin/java"),
       "-Djava.net.preferIPv4Stack=true",      // ensure IPv4 is used
+      // Ranger KMS and its dependencies use deep reflection that is restricted
+      // by the Java module system in JDK 9+ (including JDK 17).
+      // Keep Java 8 compatibility by ignoring unknown options there.
+      "-XX:+IgnoreUnrecognizedVMOptions",
+      "--add-opens=java.base/java.lang=ALL-UNNAMED",
+      "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED",
+      "--add-opens=java.base/java.io=ALL-UNNAMED",
+      "--add-opens=java.base/java.net=ALL-UNNAMED",
+      "--add-opens=java.base/java.util=ALL-UNNAMED",
+      "--add-opens=java.base/java.util.concurrent=ALL-UNNAMED",
+      "--add-opens=java.base/sun.security.action=ALL-UNNAMED",
+      "--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED",
+      "--add-opens=java.base/com.sun.crypto.provider=ALL-UNNAMED",
+      // RangerMasterKey directly uses com.sun.org.apache.xml.internal.security.utils.Base64
+      // from the java.xml.crypto module to encode/decode the encrypted master key.
+      "--add-exports=java.xml.crypto/com.sun.org.apache.xml.internal.security.utils=ALL-UNNAMED",
       "-Dproc_rangerkms",
       Substitute("-Dhostname=$0", host_),
       Substitute("-Dlog4j.configuration=file:$0",
diff --git a/src/kudu/ranger/mini_ranger.cc b/src/kudu/ranger/mini_ranger.cc
index de577bac7..d32b0a1a4 100644
--- a/src/kudu/ranger/mini_ranger.cc
+++ b/src/kudu/ranger/mini_ranger.cc
@@ -194,6 +194,18 @@ Status MiniRanger::StartRanger() {
     std::vector<string> args({
         JoinPathSegments(java_home_, "bin/java"),
         "-Djava.net.preferIPv4Stack=true",      // ensure IPv4 is used
+        // Ranger and its dependencies use deep reflection that is restricted by
+        // the Java module system in JDK 9+ (including JDK 17).
+        // Keep Java 8 compatibility by ignoring unknown options there.
+        "-XX:+IgnoreUnrecognizedVMOptions",
+        "--add-opens=java.base/java.lang=ALL-UNNAMED",
+        "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED",
+        "--add-opens=java.base/java.io=ALL-UNNAMED",
+        "--add-opens=java.base/java.net=ALL-UNNAMED",
+        "--add-opens=java.base/java.util=ALL-UNNAMED",
+        "--add-opens=java.base/java.util.concurrent=ALL-UNNAMED",
+        "--add-opens=java.base/sun.security.action=ALL-UNNAMED",
+        "--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED",
         "-Dproc_rangeradmin",
         Substitute("-Dhostname=$0", host_),
         Substitute("-Dlog4j.configuration=file:$0",