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

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new d6cdd4dc0 fix(java/driver/jni): handle "modified UTF-8" encoding 
(#4423)
d6cdd4dc0 is described below

commit d6cdd4dc044d2ef110dc7bc1b78b1526dd3dc60b
Author: David Li <[email protected]>
AuthorDate: Tue Jun 23 17:32:18 2026 -0700

    fix(java/driver/jni): handle "modified UTF-8" encoding (#4423)
    
    Assisted-by: GPT-5.5 <[email protected]>
---
 java/driver/jni/src/main/cpp/jni_wrapper.cc        | 339 +++++++++++----------
 .../arrow/adbc/driver/jni/impl/JniLoader.java      | 103 ++++---
 .../arrow/adbc/driver/jni/impl/NativeAdbc.java     |  70 ++---
 .../arrow/adbc/driver/jni/JniDriverTest.java       |  23 ++
 4 files changed, 296 insertions(+), 239 deletions(-)

diff --git a/java/driver/jni/src/main/cpp/jni_wrapper.cc 
b/java/driver/jni/src/main/cpp/jni_wrapper.cc
index 56e5e4718..f39e3538b 100644
--- a/java/driver/jni/src/main/cpp/jni_wrapper.cc
+++ b/java/driver/jni/src/main/cpp/jni_wrapper.cc
@@ -216,51 +216,47 @@ jmethodID RequireMethod(JNIEnv* env, jclass klass, 
std::string_view name,
   return method;
 }
 
-struct JniStringView {
-  JNIEnv* env;
-  jstring jni_string;
-  const char* value;
-
-  explicit JniStringView(JNIEnv* env, jstring jni_string)
-      : env(env), jni_string(jni_string), value(nullptr) {
-    if (jni_string == nullptr) {
-      throw AdbcException{ADBC_STATUS_INTERNAL, "Java string was nullptr"};
-    }
-    value = env->GetStringUTFChars(jni_string, nullptr);
-    if (value == nullptr) {
-      throw AdbcException{ADBC_STATUS_INTERNAL,
-                          "Java string was nullptr (could not get string 
contents)"};
-    }
+std::string GetJniUtf8String(JNIEnv* env, jbyteArray jni_string) {
+  if (jni_string == nullptr) {
+    throw AdbcException{ADBC_STATUS_INTERNAL, "Java byte array was nullptr"};
   }
 
-  ~JniStringView() {
-    if (jni_string == nullptr) {
-      return;
-    }
-
-    env->ReleaseStringUTFChars(jni_string, value);
-    env->DeleteLocalRef(jni_string);
-    jni_string = nullptr;
+  jsize length = env->GetArrayLength(jni_string);
+  if (env->ExceptionCheck()) {
+    throw AdbcException{ADBC_STATUS_INTERNAL, "Could not get byte array 
length"};
   }
-};
 
-std::string GetJniString(JNIEnv* env, jstring jni_string) {
-  JniStringView view(env, jni_string);
-  return std::string(view.value);
+  std::string result(static_cast<size_t>(length), '\0');
+  if (length > 0) {
+    env->GetByteArrayRegion(jni_string, 0, length,
+                            reinterpret_cast<jbyte*>(result.data()));
+    if (env->ExceptionCheck()) {
+      throw AdbcException{ADBC_STATUS_INTERNAL, "Could not get byte array 
contents"};
+    }
+  }
+  return result;
 }
 
-std::optional<std::string> MaybeGetJniString(JNIEnv* env, jstring jni_string) {
+std::optional<std::string> MaybeGetJniUtf8String(JNIEnv* env, jbyteArray 
jni_string) {
   if (jni_string == nullptr) {
     return std::nullopt;
   }
-  JniStringView view(env, jni_string);
-  return std::string(view.value);
+  return GetJniUtf8String(env, jni_string);
 }
 
-template <typename Callable>
-auto WithJniString(JNIEnv* env, jstring jni_string, Callable&& callable) {
-  JniStringView view(env, jni_string);
-  return callable(view.value);
+jbyteArray MakeJniUtf8String(JNIEnv* env, const char* value, size_t length) {
+  if (length > 0 && value[length - 1] == '\0') {
+    length--;
+  }
+
+  jbyteArray result = env->NewByteArray(static_cast<jsize>(length));
+  if (result == nullptr || env->ExceptionCheck()) return nullptr;
+  if (length > 0) {
+    env->SetByteArrayRegion(result, 0, static_cast<jsize>(length),
+                            reinterpret_cast<const jbyte*>(value));
+    if (env->ExceptionCheck()) return nullptr;
+  }
+  return result;
 }
 
 // Get the contents of a ByteBuffer. We may have to copy, so also take a
@@ -430,17 +426,18 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_openDatabase(
                      error_guard.value);
 
     for (jsize i = 0; i < num_params; i += 2) {
-      // N.B. assuming String because Java side is typed as String[]
-      auto key = 
reinterpret_cast<jstring>(env->GetObjectArrayElement(parameters, i));
+      auto key = 
reinterpret_cast<jbyteArray>(env->GetObjectArrayElement(parameters, i));
       if (env->ExceptionCheck()) return nullptr;
       auto value =
-          reinterpret_cast<jstring>(env->GetObjectArrayElement(parameters, i + 
1));
+          reinterpret_cast<jbyteArray>(env->GetObjectArrayElement(parameters, 
i + 1));
       if (env->ExceptionCheck()) return nullptr;
 
-      JniStringView key_str(env, key);
-      JniStringView value_str(env, value);
-      CHECK_ADBC_ERROR(AdbcDatabaseSetOption(&db.value, key_str.value, 
value_str.value,
-                                             &error_guard.value),
+      std::string key_str = GetJniUtf8String(env, key);
+      std::string value_str = GetJniUtf8String(env, value);
+      env->DeleteLocalRef(key);
+      env->DeleteLocalRef(value);
+      CHECK_ADBC_ERROR(AdbcDatabaseSetOption(&db.value, key_str.c_str(),
+                                             value_str.c_str(), 
&error_guard.value),
                        error_guard.value);
     }
     CHECK_ADBC_ERROR(AdbcDatabaseInit(&db.value, &error_guard.value), 
error_guard.value);
@@ -686,12 +683,12 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementExecuteQuery(
 
 JNIEXPORT void JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementSetSqlQuery(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring query) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray query) 
{
   AdbcGuard<struct AdbcError> error_guard;
   auto* ptr = reinterpret_cast<struct 
AdbcStatement*>(static_cast<uintptr_t>(handle));
   try {
-    JniStringView query_str(env, query);
-    CHECK_ADBC_ERROR(AdbcStatementSetSqlQuery(ptr, query_str.value, 
&error_guard.value),
+    std::string query_str = GetJniUtf8String(env, query);
+    CHECK_ADBC_ERROR(AdbcStatementSetSqlQuery(ptr, query_str.c_str(), 
&error_guard.value),
                      error_guard.value);
   } catch (const AdbcException& e) {
     e.ThrowJavaException(env);
@@ -798,22 +795,22 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementExecuteSchema(
 
 JNIEXPORT jbyteArray JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementGetOptionBytes(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* stmt = reinterpret_cast<struct 
AdbcStatement*>(static_cast<uintptr_t>(handle));
 
   std::vector<uint8_t> buf(1024, '\0');
   size_t length = buf.size();
   try {
-    JniStringView key_str(env, key);
-    CHECK_ADBC_ERROR(
-        AdbcStatementGetOptionBytes(stmt, key_str.value, 
const_cast<uint8_t*>(buf.data()),
-                                    &length, &error_guard.value),
-        error_guard.value);
+    std::string key_str = GetJniUtf8String(env, key);
+    CHECK_ADBC_ERROR(AdbcStatementGetOptionBytes(stmt, key_str.c_str(),
+                                                 
const_cast<uint8_t*>(buf.data()),
+                                                 &length, &error_guard.value),
+                     error_guard.value);
     while (length > buf.size()) {
       // Buffer was too small, resize and try again
       buf.resize(length);
-      CHECK_ADBC_ERROR(AdbcStatementGetOptionBytes(stmt, key_str.value,
+      CHECK_ADBC_ERROR(AdbcStatementGetOptionBytes(stmt, key_str.c_str(),
                                                    
const_cast<uint8_t*>(buf.data()),
                                                    &length, 
&error_guard.value),
                        error_guard.value);
@@ -832,14 +829,14 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementGetOptionBytes(
 
 JNIEXPORT jdouble JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementGetOptionDouble(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* stmt = reinterpret_cast<struct 
AdbcStatement*>(static_cast<uintptr_t>(handle));
   double value = 0.0;
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcStatementGetOptionDouble(stmt, key_str.value, &value, 
&error_guard.value),
+        AdbcStatementGetOptionDouble(stmt, key_str.c_str(), &value, 
&error_guard.value),
         error_guard.value);
   } catch (const AdbcException& e) {
     e.ThrowJavaException(env);
@@ -850,14 +847,14 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementGetOptionDouble(
 
 JNIEXPORT jlong JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementGetOptionLong(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* stmt = reinterpret_cast<struct 
AdbcStatement*>(static_cast<uintptr_t>(handle));
   int64_t value = 0;
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcStatementGetOptionInt(stmt, key_str.value, &value, 
&error_guard.value),
+        AdbcStatementGetOptionInt(stmt, key_str.c_str(), &value, 
&error_guard.value),
         error_guard.value);
   } catch (const AdbcException& e) {
     e.ThrowJavaException(env);
@@ -866,25 +863,25 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementGetOptionLong(
   return static_cast<jlong>(value);
 }
 
-JNIEXPORT jstring JNICALL
+JNIEXPORT jbyteArray JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementGetOptionString(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* stmt = reinterpret_cast<struct 
AdbcStatement*>(static_cast<uintptr_t>(handle));
 
   std::vector<char> buf(1024, '\0');
   size_t length = buf.size();
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcStatementGetOption(stmt, key_str.value, 
const_cast<char*>(buf.data()),
+        AdbcStatementGetOption(stmt, key_str.c_str(), 
const_cast<char*>(buf.data()),
                                &length, &error_guard.value),
         error_guard.value);
     while (length > buf.size()) {
       // Buffer was too small, resize and try again
       buf.resize(length);
       CHECK_ADBC_ERROR(
-          AdbcStatementGetOption(stmt, key_str.value, 
const_cast<char*>(buf.data()),
+          AdbcStatementGetOption(stmt, key_str.c_str(), 
const_cast<char*>(buf.data()),
                                  &length, &error_guard.value),
           error_guard.value);
     }
@@ -892,24 +889,24 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementGetOptionString(
     e.ThrowJavaException(env);
     return nullptr;
   }
-  return env->NewStringUTF(buf.data());
+  return MakeJniUtf8String(env, buf.data(), length);
 }
 
 JNIEXPORT void JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementSetOptionBytes(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key,
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key,
     jbyteArray value) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* stmt = reinterpret_cast<struct 
AdbcStatement*>(static_cast<uintptr_t>(handle));
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     jsize value_length = env->GetArrayLength(value);
     if (env->ExceptionCheck()) return;
     std::vector<uint8_t> value_buf(static_cast<size_t>(value_length));
     env->GetByteArrayRegion(value, 0, value_length,
                             reinterpret_cast<jbyte*>(value_buf.data()));
     if (env->ExceptionCheck()) return;
-    CHECK_ADBC_ERROR(AdbcStatementSetOptionBytes(stmt, key_str.value, 
value_buf.data(),
+    CHECK_ADBC_ERROR(AdbcStatementSetOptionBytes(stmt, key_str.c_str(), 
value_buf.data(),
                                                  value_buf.size(), 
&error_guard.value),
                      error_guard.value);
   } catch (const AdbcException& e) {
@@ -919,13 +916,14 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementSetOptionBytes(
 
 JNIEXPORT void JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementSetOptionDouble(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key, 
jdouble value) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key,
+    jdouble value) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* stmt = reinterpret_cast<struct 
AdbcStatement*>(static_cast<uintptr_t>(handle));
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcStatementSetOptionDouble(stmt, key_str.value, 
static_cast<double>(value),
+        AdbcStatementSetOptionDouble(stmt, key_str.c_str(), 
static_cast<double>(value),
                                      &error_guard.value),
         error_guard.value);
   } catch (const AdbcException& e) {
@@ -935,13 +933,14 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementSetOptionDouble(
 
 JNIEXPORT void JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementSetOptionLong(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key, 
jlong value) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key,
+    jlong value) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* stmt = reinterpret_cast<struct 
AdbcStatement*>(static_cast<uintptr_t>(handle));
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcStatementSetOptionInt(stmt, key_str.value, 
static_cast<int64_t>(value),
+        AdbcStatementSetOptionInt(stmt, key_str.c_str(), 
static_cast<int64_t>(value),
                                   &error_guard.value),
         error_guard.value);
   } catch (const AdbcException& e) {
@@ -951,21 +950,22 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementSetOptionLong(
 
 JNIEXPORT void JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementSetOptionString(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key, 
jstring value) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key,
+    jbyteArray value) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* stmt = reinterpret_cast<struct 
AdbcStatement*>(static_cast<uintptr_t>(handle));
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     if (value == nullptr) {
       CHECK_ADBC_ERROR(
-          AdbcStatementSetOption(stmt, key_str.value, nullptr, 
&error_guard.value),
+          AdbcStatementSetOption(stmt, key_str.c_str(), nullptr, 
&error_guard.value),
           error_guard.value);
       return;
     }
-    JniStringView value_str(env, value);
-    CHECK_ADBC_ERROR(
-        AdbcStatementSetOption(stmt, key_str.value, value_str.value, 
&error_guard.value),
-        error_guard.value);
+    std::string value_str = GetJniUtf8String(env, value);
+    CHECK_ADBC_ERROR(AdbcStatementSetOption(stmt, key_str.c_str(), 
value_str.c_str(),
+                                            &error_guard.value),
+                     error_guard.value);
   } catch (const AdbcException& e) {
     e.ThrowJavaException(env);
   }
@@ -985,20 +985,18 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionCancel(
 
 JNIEXPORT jobject JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetObjects(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jint depth, 
jstring catalog,
-    jstring db_schema, jstring table_name, jobjectArray table_types,
-    jstring column_name) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jint depth,
+    jbyteArray catalog, jbyteArray db_schema, jbyteArray table_name,
+    jobjectArray table_types, jbyteArray column_name) {
   AdbcGuard<struct AdbcError> error_guard;
   try {
     auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
 
-    // Nullable string parameters: null jstring → NULL for C API (meaning "no 
filter")
-    auto catalog_str = MaybeGetJniString(env, catalog);
-    auto db_schema_str = MaybeGetJniString(env, db_schema);
-    auto table_name_str = MaybeGetJniString(env, table_name);
-    auto column_name_str = MaybeGetJniString(env, column_name);
+    auto catalog_str = MaybeGetJniUtf8String(env, catalog);
+    auto db_schema_str = MaybeGetJniUtf8String(env, db_schema);
+    auto table_name_str = MaybeGetJniUtf8String(env, table_name);
+    auto column_name_str = MaybeGetJniUtf8String(env, column_name);
 
-    // Convert String[] table_types to const char** (NULL-terminated) or NULL
     std::vector<std::string> table_type_strings;
     std::vector<const char*> table_type_ptrs;
     const char** c_table_types = nullptr;
@@ -1009,9 +1007,10 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetObjects(
       table_type_ptrs.reserve(len + 1);
       for (jsize i = 0; i < len; i++) {
         auto element =
-            reinterpret_cast<jstring>(env->GetObjectArrayElement(table_types, 
i));
+            
reinterpret_cast<jbyteArray>(env->GetObjectArrayElement(table_types, i));
         if (env->ExceptionCheck()) return nullptr;
-        table_type_strings.push_back(GetJniString(env, element));
+        table_type_strings.push_back(GetJniUtf8String(env, element));
+        env->DeleteLocalRef(element);
         table_type_ptrs.push_back(table_type_strings.back().c_str());
       }
       table_type_ptrs.push_back(nullptr);  // NULL terminator
@@ -1073,22 +1072,22 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetInfo(
 
 JNIEXPORT jobject JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetTableSchema(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring catalog,
-    jstring db_schema, jstring table_name) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray 
catalog,
+    jbyteArray db_schema, jbyteArray table_name) {
   AdbcGuard<struct AdbcError> error_guard;
   try {
     auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
 
-    auto catalog_str = MaybeGetJniString(env, catalog);
-    auto db_schema_str = MaybeGetJniString(env, db_schema);
-    JniStringView table_name_str(env, table_name);
+    auto catalog_str = MaybeGetJniUtf8String(env, catalog);
+    auto db_schema_str = MaybeGetJniUtf8String(env, db_schema);
+    std::string table_name_str = GetJniUtf8String(env, table_name);
 
     struct ArrowSchema schema = {};
 
     CHECK_ADBC_ERROR(
         AdbcConnectionGetTableSchema(conn, catalog_str ? catalog_str->c_str() 
: nullptr,
                                      db_schema_str ? db_schema_str->c_str() : 
nullptr,
-                                     table_name_str.value, &schema, 
&error_guard.value),
+                                     table_name_str.c_str(), &schema, 
&error_guard.value),
         error_guard.value);
 
     return MakeNativeSchemaResult(env, &schema);
@@ -1119,22 +1118,22 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetTableTypes(
 
 JNIEXPORT jbyteArray JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetOptionBytes(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
 
   std::vector<uint8_t> buf(1024, '\0');
   size_t length = buf.size();
   try {
-    JniStringView key_str(env, key);
-    CHECK_ADBC_ERROR(AdbcConnectionGetOptionBytes(conn, key_str.value,
+    std::string key_str = GetJniUtf8String(env, key);
+    CHECK_ADBC_ERROR(AdbcConnectionGetOptionBytes(conn, key_str.c_str(),
                                                   
const_cast<uint8_t*>(buf.data()),
                                                   &length, &error_guard.value),
                      error_guard.value);
     while (length > buf.size()) {
       // Buffer was too small, resize and try again
       buf.resize(length);
-      CHECK_ADBC_ERROR(AdbcConnectionGetOptionBytes(conn, key_str.value,
+      CHECK_ADBC_ERROR(AdbcConnectionGetOptionBytes(conn, key_str.c_str(),
                                                     
const_cast<uint8_t*>(buf.data()),
                                                     &length, 
&error_guard.value),
                        error_guard.value);
@@ -1153,14 +1152,14 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetOptionBytes(
 
 JNIEXPORT jdouble JNICALL
 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetOptionDouble(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
   double value = 0.0;
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcConnectionGetOptionDouble(conn, key_str.value, &value, 
&error_guard.value),
+        AdbcConnectionGetOptionDouble(conn, key_str.c_str(), &value, 
&error_guard.value),
         error_guard.value);
   } catch (const AdbcException& e) {
     e.ThrowJavaException(env);
@@ -1171,14 +1170,14 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetOptionDouble(
 
 JNIEXPORT jlong JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetOptionLong(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
   int64_t value = 0;
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcConnectionGetOptionInt(conn, key_str.value, &value, 
&error_guard.value),
+        AdbcConnectionGetOptionInt(conn, key_str.c_str(), &value, 
&error_guard.value),
         error_guard.value);
   } catch (const AdbcException& e) {
     e.ThrowJavaException(env);
@@ -1187,25 +1186,25 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetOptionLong(
   return static_cast<jlong>(value);
 }
 
-JNIEXPORT jstring JNICALL
+JNIEXPORT jbyteArray JNICALL
 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetOptionString(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
 
   std::vector<char> buf(1024, '\0');
   size_t length = buf.size();
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcConnectionGetOption(conn, key_str.value, 
const_cast<char*>(buf.data()),
+        AdbcConnectionGetOption(conn, key_str.c_str(), 
const_cast<char*>(buf.data()),
                                 &length, &error_guard.value),
         error_guard.value);
     while (length > buf.size()) {
       // Buffer was too small, resize and try again
       buf.resize(length);
       CHECK_ADBC_ERROR(
-          AdbcConnectionGetOption(conn, key_str.value, 
const_cast<char*>(buf.data()),
+          AdbcConnectionGetOption(conn, key_str.c_str(), 
const_cast<char*>(buf.data()),
                                   &length, &error_guard.value),
           error_guard.value);
     }
@@ -1213,24 +1212,24 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetOptionString(
     e.ThrowJavaException(env);
     return nullptr;
   }
-  return env->NewStringUTF(buf.data());
+  return MakeJniUtf8String(env, buf.data(), length);
 }
 
 JNIEXPORT void JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionSetOptionBytes(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key,
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key,
     jbyteArray value) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     jsize value_length = env->GetArrayLength(value);
     if (env->ExceptionCheck()) return;
     std::vector<uint8_t> value_buf(static_cast<size_t>(value_length));
     env->GetByteArrayRegion(value, 0, value_length,
                             reinterpret_cast<jbyte*>(value_buf.data()));
     if (env->ExceptionCheck()) return;
-    CHECK_ADBC_ERROR(AdbcConnectionSetOptionBytes(conn, key_str.value, 
value_buf.data(),
+    CHECK_ADBC_ERROR(AdbcConnectionSetOptionBytes(conn, key_str.c_str(), 
value_buf.data(),
                                                   value_buf.size(), 
&error_guard.value),
                      error_guard.value);
   } catch (const AdbcException& e) {
@@ -1240,13 +1239,14 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionSetOptionBytes(
 
 JNIEXPORT void JNICALL
 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionSetOptionDouble(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key, 
jdouble value) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key,
+    jdouble value) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcConnectionSetOptionDouble(conn, key_str.value, 
static_cast<double>(value),
+        AdbcConnectionSetOptionDouble(conn, key_str.c_str(), 
static_cast<double>(value),
                                       &error_guard.value),
         error_guard.value);
   } catch (const AdbcException& e) {
@@ -1256,13 +1256,14 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionSetOptionDouble(
 
 JNIEXPORT void JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionSetOptionLong(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key, 
jlong value) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key,
+    jlong value) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcConnectionSetOptionInt(conn, key_str.value, 
static_cast<int64_t>(value),
+        AdbcConnectionSetOptionInt(conn, key_str.c_str(), 
static_cast<int64_t>(value),
                                    &error_guard.value),
         error_guard.value);
   } catch (const AdbcException& e) {
@@ -1272,21 +1273,22 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionSetOptionLong(
 
 JNIEXPORT void JNICALL
 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionSetOptionString(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key, 
jstring value) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key,
+    jbyteArray value) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     if (value == nullptr) {
       CHECK_ADBC_ERROR(
-          AdbcConnectionSetOption(conn, key_str.value, nullptr, 
&error_guard.value),
+          AdbcConnectionSetOption(conn, key_str.c_str(), nullptr, 
&error_guard.value),
           error_guard.value);
       return;
     }
-    JniStringView value_str(env, value);
-    CHECK_ADBC_ERROR(
-        AdbcConnectionSetOption(conn, key_str.value, value_str.value, 
&error_guard.value),
-        error_guard.value);
+    std::string value_str = GetJniUtf8String(env, value);
+    CHECK_ADBC_ERROR(AdbcConnectionSetOption(conn, key_str.c_str(), 
value_str.c_str(),
+                                             &error_guard.value),
+                     error_guard.value);
   } catch (const AdbcException& e) {
     e.ThrowJavaException(env);
   }
@@ -1361,15 +1363,15 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetStatisticName
 
 JNIEXPORT jobject JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetStatistics(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring catalog,
-    jstring schema, jstring table, jboolean approximate) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray 
catalog,
+    jbyteArray schema, jbyteArray table, jboolean approximate) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
   struct ArrowArrayStream out = {};
   try {
-    std::optional<std::string> catalog_str = MaybeGetJniString(env, catalog);
-    std::optional<std::string> schema_str = MaybeGetJniString(env, schema);
-    std::optional<std::string> table_str = MaybeGetJniString(env, table);
+    std::optional<std::string> catalog_str = MaybeGetJniUtf8String(env, 
catalog);
+    std::optional<std::string> schema_str = MaybeGetJniUtf8String(env, schema);
+    std::optional<std::string> table_str = MaybeGetJniUtf8String(env, table);
     CHECK_ADBC_ERROR(
         AdbcConnectionGetStatistics(conn, catalog_str ? catalog_str->c_str() : 
nullptr,
                                     schema_str ? schema_str->c_str() : nullptr,
@@ -1385,25 +1387,25 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetStatistics(
 
 JNIEXPORT jbyteArray JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseGetOptionBytes(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* db = reinterpret_cast<struct 
AdbcDatabase*>(static_cast<uintptr_t>(handle));
 
   std::vector<uint8_t> buf(1024, '\0');
   size_t length = buf.size();
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcDatabaseGetOptionBytes(db, key_str.value, 
const_cast<uint8_t*>(buf.data()),
+        AdbcDatabaseGetOptionBytes(db, key_str.c_str(), 
const_cast<uint8_t*>(buf.data()),
                                    &length, &error_guard.value),
         error_guard.value);
     while (length > buf.size()) {
       // Buffer was too small, resize and try again
       buf.resize(length);
-      CHECK_ADBC_ERROR(
-          AdbcDatabaseGetOptionBytes(db, key_str.value, 
const_cast<uint8_t*>(buf.data()),
-                                     &length, &error_guard.value),
-          error_guard.value);
+      CHECK_ADBC_ERROR(AdbcDatabaseGetOptionBytes(db, key_str.c_str(),
+                                                  
const_cast<uint8_t*>(buf.data()),
+                                                  &length, &error_guard.value),
+                       error_guard.value);
     }
   } catch (const AdbcException& e) {
     e.ThrowJavaException(env);
@@ -1419,14 +1421,14 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseGetOptionBytes(
 
 JNIEXPORT jdouble JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseGetOptionDouble(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* db = reinterpret_cast<struct 
AdbcDatabase*>(static_cast<uintptr_t>(handle));
   double value = 0.0;
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcDatabaseGetOptionDouble(db, key_str.value, &value, 
&error_guard.value),
+        AdbcDatabaseGetOptionDouble(db, key_str.c_str(), &value, 
&error_guard.value),
         error_guard.value);
   } catch (const AdbcException& e) {
     e.ThrowJavaException(env);
@@ -1437,14 +1439,14 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseGetOptionDouble(
 
 JNIEXPORT jlong JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseGetOptionLong(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* db = reinterpret_cast<struct 
AdbcDatabase*>(static_cast<uintptr_t>(handle));
   int64_t value = 0;
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcDatabaseGetOptionInt(db, key_str.value, &value, 
&error_guard.value),
+        AdbcDatabaseGetOptionInt(db, key_str.c_str(), &value, 
&error_guard.value),
         error_guard.value);
   } catch (const AdbcException& e) {
     e.ThrowJavaException(env);
@@ -1453,50 +1455,50 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseGetOptionLong(
   return static_cast<jlong>(value);
 }
 
-JNIEXPORT jstring JNICALL
+JNIEXPORT jbyteArray JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseGetOptionString(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* db = reinterpret_cast<struct 
AdbcDatabase*>(static_cast<uintptr_t>(handle));
 
   std::vector<char> buf(1024, '\0');
   size_t length = buf.size();
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcDatabaseGetOption(db, key_str.value, 
const_cast<char*>(buf.data()), &length,
+        AdbcDatabaseGetOption(db, key_str.c_str(), 
const_cast<char*>(buf.data()), &length,
                               &error_guard.value),
         error_guard.value);
     while (length > buf.size()) {
       // Buffer was too small, resize and try again
       buf.resize(length);
       CHECK_ADBC_ERROR(
-          AdbcDatabaseGetOption(db, key_str.value, 
const_cast<char*>(buf.data()), &length,
-                                &error_guard.value),
+          AdbcDatabaseGetOption(db, key_str.c_str(), 
const_cast<char*>(buf.data()),
+                                &length, &error_guard.value),
           error_guard.value);
     }
   } catch (const AdbcException& e) {
     e.ThrowJavaException(env);
     return nullptr;
   }
-  return env->NewStringUTF(buf.data());
+  return MakeJniUtf8String(env, buf.data(), length);
 }
 
 JNIEXPORT void JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseSetOptionBytes(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key,
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key,
     jbyteArray value) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* db = reinterpret_cast<struct 
AdbcDatabase*>(static_cast<uintptr_t>(handle));
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     jsize value_length = env->GetArrayLength(value);
     if (env->ExceptionCheck()) return;
     std::vector<uint8_t> value_buf(static_cast<size_t>(value_length));
     env->GetByteArrayRegion(value, 0, value_length,
                             reinterpret_cast<jbyte*>(value_buf.data()));
     if (env->ExceptionCheck()) return;
-    CHECK_ADBC_ERROR(AdbcDatabaseSetOptionBytes(db, key_str.value, 
value_buf.data(),
+    CHECK_ADBC_ERROR(AdbcDatabaseSetOptionBytes(db, key_str.c_str(), 
value_buf.data(),
                                                 value_buf.size(), 
&error_guard.value),
                      error_guard.value);
   } catch (const AdbcException& e) {
@@ -1506,13 +1508,14 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseSetOptionBytes(
 
 JNIEXPORT void JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseSetOptionDouble(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key, 
jdouble value) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key,
+    jdouble value) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* db = reinterpret_cast<struct 
AdbcDatabase*>(static_cast<uintptr_t>(handle));
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcDatabaseSetOptionDouble(db, key_str.value, 
static_cast<double>(value),
+        AdbcDatabaseSetOptionDouble(db, key_str.c_str(), 
static_cast<double>(value),
                                     &error_guard.value),
         error_guard.value);
   } catch (const AdbcException& e) {
@@ -1522,13 +1525,14 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseSetOptionDouble(
 
 JNIEXPORT void JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseSetOptionLong(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key, 
jlong value) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key,
+    jlong value) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* db = reinterpret_cast<struct 
AdbcDatabase*>(static_cast<uintptr_t>(handle));
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     CHECK_ADBC_ERROR(
-        AdbcDatabaseSetOptionInt(db, key_str.value, 
static_cast<int64_t>(value),
+        AdbcDatabaseSetOptionInt(db, key_str.c_str(), 
static_cast<int64_t>(value),
                                  &error_guard.value),
         error_guard.value);
   } catch (const AdbcException& e) {
@@ -1538,20 +1542,21 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseSetOptionLong(
 
 JNIEXPORT void JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseSetOptionString(
-    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key, 
jstring value) {
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jbyteArray key,
+    jbyteArray value) {
   AdbcGuard<struct AdbcError> error_guard;
   auto* db = reinterpret_cast<struct 
AdbcDatabase*>(static_cast<uintptr_t>(handle));
   try {
-    JniStringView key_str(env, key);
+    std::string key_str = GetJniUtf8String(env, key);
     if (value == nullptr) {
       CHECK_ADBC_ERROR(
-          AdbcDatabaseSetOption(db, key_str.value, nullptr, 
&error_guard.value),
+          AdbcDatabaseSetOption(db, key_str.c_str(), nullptr, 
&error_guard.value),
           error_guard.value);
       return;
     }
-    JniStringView value_str(env, value);
+    std::string value_str = GetJniUtf8String(env, value);
     CHECK_ADBC_ERROR(
-        AdbcDatabaseSetOption(db, key_str.value, value_str.value, 
&error_guard.value),
+        AdbcDatabaseSetOption(db, key_str.c_str(), value_str.c_str(), 
&error_guard.value),
         error_guard.value);
   } catch (const AdbcException& e) {
     e.ThrowJavaException(env);
diff --git 
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/JniLoader.java
 
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/JniLoader.java
index 3174655c5..e2934741c 100644
--- 
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/JniLoader.java
+++ 
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/JniLoader.java
@@ -22,6 +22,7 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.StandardCopyOption;
 import java.util.Map;
@@ -65,11 +66,11 @@ public enum JniLoader {
   }
 
   public NativeDatabaseHandle openDatabase(Map<String, String> parameters) 
throws AdbcException {
-    String[] nativeParameters = new String[parameters.size() * 2];
+    byte[][] nativeParameters = new byte[parameters.size() * 2][];
     int index = 0;
     for (Map.Entry<String, String> parameter : parameters.entrySet()) {
-      nativeParameters[index++] = parameter.getKey();
-      nativeParameters[index++] = parameter.getValue();
+      nativeParameters[index++] = stringToUtf8(parameter.getKey());
+      nativeParameters[index++] = stringToUtf8(parameter.getValue());
     }
     return NativeAdbc.openDatabase(1001000, nativeParameters);
   }
@@ -99,7 +100,7 @@ public enum JniLoader {
 
   public void statementSetSqlQuery(NativeStatementHandle statement, String 
query)
       throws AdbcException {
-    NativeAdbc.statementSetSqlQuery(statement.getStatementHandle(), query);
+    NativeAdbc.statementSetSqlQuery(statement.getStatementHandle(), 
stringToUtf8(query));
   }
 
   public void statementSetSubstraitPlan(NativeStatementHandle statement, 
ByteBuffer plan)
@@ -138,42 +139,44 @@ public enum JniLoader {
 
   public byte[] statementGetOptionBytes(NativeStatementHandle handle, String 
key)
       throws AdbcException {
-    return NativeAdbc.statementGetOptionBytes(handle.getStatementHandle(), 
key);
+    return NativeAdbc.statementGetOptionBytes(handle.getStatementHandle(), 
stringToUtf8(key));
   }
 
   public double statementGetOptionDouble(NativeStatementHandle handle, String 
key)
       throws AdbcException {
-    return NativeAdbc.statementGetOptionDouble(handle.getStatementHandle(), 
key);
+    return NativeAdbc.statementGetOptionDouble(handle.getStatementHandle(), 
stringToUtf8(key));
   }
 
   public long statementGetOptionLong(NativeStatementHandle handle, String key)
       throws AdbcException {
-    return NativeAdbc.statementGetOptionLong(handle.getStatementHandle(), key);
+    return NativeAdbc.statementGetOptionLong(handle.getStatementHandle(), 
stringToUtf8(key));
   }
 
   public String statementGetOptionString(NativeStatementHandle handle, String 
key)
       throws AdbcException {
-    return NativeAdbc.statementGetOptionString(handle.getStatementHandle(), 
key);
+    return utf8ToString(
+        NativeAdbc.statementGetOptionString(handle.getStatementHandle(), 
stringToUtf8(key)));
   }
 
   public void statementSetOptionBytes(NativeStatementHandle handle, String 
key, byte[] value)
       throws AdbcException {
-    NativeAdbc.statementSetOptionBytes(handle.getStatementHandle(), key, 
value);
+    NativeAdbc.statementSetOptionBytes(handle.getStatementHandle(), 
stringToUtf8(key), value);
   }
 
   public void statementSetOptionDouble(NativeStatementHandle handle, String 
key, double value)
       throws AdbcException {
-    NativeAdbc.statementSetOptionDouble(handle.getStatementHandle(), key, 
value);
+    NativeAdbc.statementSetOptionDouble(handle.getStatementHandle(), 
stringToUtf8(key), value);
   }
 
   public void statementSetOptionLong(NativeStatementHandle handle, String key, 
long value)
       throws AdbcException {
-    NativeAdbc.statementSetOptionLong(handle.getStatementHandle(), key, value);
+    NativeAdbc.statementSetOptionLong(handle.getStatementHandle(), 
stringToUtf8(key), value);
   }
 
   public void statementSetOptionString(NativeStatementHandle statement, String 
key, String value)
       throws AdbcException {
-    NativeAdbc.statementSetOptionString(statement.getStatementHandle(), key, 
value);
+    NativeAdbc.statementSetOptionString(
+        statement.getStatementHandle(), stringToUtf8(key), 
stringToUtf8(value));
   }
 
   public void connectionCancel(NativeConnectionHandle connection) throws 
AdbcException {
@@ -192,11 +195,11 @@ public enum JniLoader {
     return NativeAdbc.connectionGetObjects(
         connection.getConnectionHandle(),
         depth,
-        catalog,
-        dbSchema,
-        tableName,
-        tableTypes,
-        columnName);
+        stringToUtf8(catalog),
+        stringToUtf8(dbSchema),
+        stringToUtf8(tableName),
+        stringArrayToUtf8(tableTypes),
+        stringToUtf8(columnName));
   }
 
   public NativeQueryResult connectionGetInfo(NativeConnectionHandle 
connection, int[] infoCodes)
@@ -208,7 +211,10 @@ public enum JniLoader {
       NativeConnectionHandle connection, String catalog, String dbSchema, 
String tableName)
       throws AdbcException {
     return NativeAdbc.connectionGetTableSchema(
-        connection.getConnectionHandle(), catalog, dbSchema, tableName);
+        connection.getConnectionHandle(),
+        stringToUtf8(catalog),
+        stringToUtf8(dbSchema),
+        stringToUtf8(tableName));
   }
 
   public NativeQueryResult connectionGetTableTypes(NativeConnectionHandle 
connection)
@@ -231,42 +237,44 @@ public enum JniLoader {
 
   public byte[] connectionGetOptionBytes(NativeConnectionHandle handle, String 
key)
       throws AdbcException {
-    return NativeAdbc.connectionGetOptionBytes(handle.getConnectionHandle(), 
key);
+    return NativeAdbc.connectionGetOptionBytes(handle.getConnectionHandle(), 
stringToUtf8(key));
   }
 
   public double connectionGetOptionDouble(NativeConnectionHandle handle, 
String key)
       throws AdbcException {
-    return NativeAdbc.connectionGetOptionDouble(handle.getConnectionHandle(), 
key);
+    return NativeAdbc.connectionGetOptionDouble(handle.getConnectionHandle(), 
stringToUtf8(key));
   }
 
   public long connectionGetOptionLong(NativeConnectionHandle handle, String 
key)
       throws AdbcException {
-    return NativeAdbc.connectionGetOptionLong(handle.getConnectionHandle(), 
key);
+    return NativeAdbc.connectionGetOptionLong(handle.getConnectionHandle(), 
stringToUtf8(key));
   }
 
   public String connectionGetOptionString(NativeConnectionHandle handle, 
String key)
       throws AdbcException {
-    return NativeAdbc.connectionGetOptionString(handle.getConnectionHandle(), 
key);
+    return utf8ToString(
+        NativeAdbc.connectionGetOptionString(handle.getConnectionHandle(), 
stringToUtf8(key)));
   }
 
   public void connectionSetOptionBytes(NativeConnectionHandle handle, String 
key, byte[] value)
       throws AdbcException {
-    NativeAdbc.connectionSetOptionBytes(handle.getConnectionHandle(), key, 
value);
+    NativeAdbc.connectionSetOptionBytes(handle.getConnectionHandle(), 
stringToUtf8(key), value);
   }
 
   public void connectionSetOptionDouble(NativeConnectionHandle handle, String 
key, double value)
       throws AdbcException {
-    NativeAdbc.connectionSetOptionDouble(handle.getConnectionHandle(), key, 
value);
+    NativeAdbc.connectionSetOptionDouble(handle.getConnectionHandle(), 
stringToUtf8(key), value);
   }
 
   public void connectionSetOptionLong(NativeConnectionHandle handle, String 
key, long value)
       throws AdbcException {
-    NativeAdbc.connectionSetOptionLong(handle.getConnectionHandle(), key, 
value);
+    NativeAdbc.connectionSetOptionLong(handle.getConnectionHandle(), 
stringToUtf8(key), value);
   }
 
   public void connectionSetOptionString(NativeConnectionHandle connection, 
String key, String value)
       throws AdbcException {
-    NativeAdbc.connectionSetOptionString(connection.getConnectionHandle(), 
key, value);
+    NativeAdbc.connectionSetOptionString(
+        connection.getConnectionHandle(), stringToUtf8(key), 
stringToUtf8(value));
   }
 
   public NativeQueryResult connectionGetStatistics(
@@ -278,9 +286,9 @@ public enum JniLoader {
       throws AdbcException {
     return NativeAdbc.connectionGetStatistics(
         connection.getConnectionHandle(),
-        catalogPattern,
-        dbSchemaPattern,
-        tableNamePattern,
+        stringToUtf8(catalogPattern),
+        stringToUtf8(dbSchemaPattern),
+        stringToUtf8(tableNamePattern),
         approximate);
   }
 
@@ -291,40 +299,61 @@ public enum JniLoader {
 
   public byte[] databaseGetOptionBytes(NativeDatabaseHandle handle, String key)
       throws AdbcException {
-    return NativeAdbc.databaseGetOptionBytes(handle.getDatabaseHandle(), key);
+    return NativeAdbc.databaseGetOptionBytes(handle.getDatabaseHandle(), 
stringToUtf8(key));
   }
 
   public double databaseGetOptionDouble(NativeDatabaseHandle handle, String 
key)
       throws AdbcException {
-    return NativeAdbc.databaseGetOptionDouble(handle.getDatabaseHandle(), key);
+    return NativeAdbc.databaseGetOptionDouble(handle.getDatabaseHandle(), 
stringToUtf8(key));
   }
 
   public long databaseGetOptionLong(NativeDatabaseHandle handle, String key) 
throws AdbcException {
-    return NativeAdbc.databaseGetOptionLong(handle.getDatabaseHandle(), key);
+    return NativeAdbc.databaseGetOptionLong(handle.getDatabaseHandle(), 
stringToUtf8(key));
   }
 
   public String databaseGetOptionString(NativeDatabaseHandle handle, String 
key)
       throws AdbcException {
-    return NativeAdbc.databaseGetOptionString(handle.getDatabaseHandle(), key);
+    return utf8ToString(
+        NativeAdbc.databaseGetOptionString(handle.getDatabaseHandle(), 
stringToUtf8(key)));
   }
 
   public void databaseSetOptionBytes(NativeDatabaseHandle handle, String key, 
byte[] value)
       throws AdbcException {
-    NativeAdbc.databaseSetOptionBytes(handle.getDatabaseHandle(), key, value);
+    NativeAdbc.databaseSetOptionBytes(handle.getDatabaseHandle(), 
stringToUtf8(key), value);
   }
 
   public void databaseSetOptionDouble(NativeDatabaseHandle handle, String key, 
double value)
       throws AdbcException {
-    NativeAdbc.databaseSetOptionDouble(handle.getDatabaseHandle(), key, value);
+    NativeAdbc.databaseSetOptionDouble(handle.getDatabaseHandle(), 
stringToUtf8(key), value);
   }
 
   public void databaseSetOptionLong(NativeDatabaseHandle handle, String key, 
long value)
       throws AdbcException {
-    NativeAdbc.databaseSetOptionLong(handle.getDatabaseHandle(), key, value);
+    NativeAdbc.databaseSetOptionLong(handle.getDatabaseHandle(), 
stringToUtf8(key), value);
   }
 
   public void databaseSetOptionString(NativeDatabaseHandle handle, String key, 
String value)
       throws AdbcException {
-    NativeAdbc.databaseSetOptionString(handle.getDatabaseHandle(), key, value);
+    NativeAdbc.databaseSetOptionString(
+        handle.getDatabaseHandle(), stringToUtf8(key), stringToUtf8(value));
+  }
+
+  private static byte[] stringToUtf8(String value) {
+    return value == null ? null : value.getBytes(StandardCharsets.UTF_8);
+  }
+
+  private static byte[][] stringArrayToUtf8(String[] values) {
+    if (values == null) {
+      return null;
+    }
+    byte[][] result = new byte[values.length][];
+    for (int i = 0; i < values.length; i++) {
+      result[i] = stringToUtf8(values[i]);
+    }
+    return result;
+  }
+
+  private static String utf8ToString(byte[] value) {
+    return value == null ? null : new String(value, StandardCharsets.UTF_8);
   }
 }
diff --git 
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java
 
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java
index c2059532f..b23245430 100644
--- 
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java
+++ 
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java
@@ -22,7 +22,7 @@ import org.apache.arrow.adbc.core.AdbcException;
 
 /** All the JNI methods. Don't use this directly, prefer {@link JniLoader}. */
 class NativeAdbc {
-  static native NativeDatabaseHandle openDatabase(int version, String[] 
parameters)
+  static native NativeDatabaseHandle openDatabase(int version, byte[][] 
parameters)
       throws AdbcException;
 
   static native void closeDatabase(long handle) throws AdbcException;
@@ -57,28 +57,28 @@ class NativeAdbc {
   static native NativeSchemaResult statementGetParameterSchema(long 
statementHandle)
       throws AdbcException;
 
-  static native void statementSetSqlQuery(long handle, String query) throws 
AdbcException;
+  static native void statementSetSqlQuery(long handle, byte[] query) throws 
AdbcException;
 
   static native void statementSetSubstraitPlan(long handle, ByteBuffer plan) 
throws AdbcException;
 
-  static native byte[] statementGetOptionBytes(long handle, String key) throws 
AdbcException;
+  static native byte[] statementGetOptionBytes(long handle, byte[] key) throws 
AdbcException;
 
-  static native double statementGetOptionDouble(long handle, String key) 
throws AdbcException;
+  static native double statementGetOptionDouble(long handle, byte[] key) 
throws AdbcException;
 
-  static native long statementGetOptionLong(long handle, String key) throws 
AdbcException;
+  static native long statementGetOptionLong(long handle, byte[] key) throws 
AdbcException;
 
-  static native String statementGetOptionString(long handle, String key) 
throws AdbcException;
+  static native byte[] statementGetOptionString(long handle, byte[] key) 
throws AdbcException;
 
-  static native void statementSetOptionBytes(long handle, String key, byte[] 
value)
+  static native void statementSetOptionBytes(long handle, byte[] key, byte[] 
value)
       throws AdbcException;
 
-  static native void statementSetOptionDouble(long handle, String key, double 
value)
+  static native void statementSetOptionDouble(long handle, byte[] key, double 
value)
       throws AdbcException;
 
-  static native void statementSetOptionLong(long handle, String key, long 
value)
+  static native void statementSetOptionLong(long handle, byte[] key, long 
value)
       throws AdbcException;
 
-  static native void statementSetOptionString(long handle, String key, String 
value)
+  static native void statementSetOptionString(long handle, byte[] key, byte[] 
value)
       throws AdbcException;
 
   static native void connectionCancel(long handle) throws AdbcException;
@@ -86,18 +86,18 @@ class NativeAdbc {
   static native NativeQueryResult connectionGetObjects(
       long handle,
       int depth,
-      String catalog,
-      String dbSchema,
-      String tableName,
-      String[] tableTypes,
-      String columnName)
+      byte[] catalog,
+      byte[] dbSchema,
+      byte[] tableName,
+      byte[][] tableTypes,
+      byte[] columnName)
       throws AdbcException;
 
   static native NativeQueryResult connectionGetInfo(long handle, int[] 
infoCodes)
       throws AdbcException;
 
   static native NativeSchemaResult connectionGetTableSchema(
-      long handle, String catalog, String dbSchema, String tableName) throws 
AdbcException;
+      long handle, byte[] catalog, byte[] dbSchema, byte[] tableName) throws 
AdbcException;
 
   static native NativeQueryResult connectionGetTableTypes(long handle) throws 
AdbcException;
 
@@ -108,53 +108,53 @@ class NativeAdbc {
   static native NativeQueryResult connectionReadPartition(long handle, 
ByteBuffer partition)
       throws AdbcException;
 
-  static native byte[] connectionGetOptionBytes(long handle, String key) 
throws AdbcException;
+  static native byte[] connectionGetOptionBytes(long handle, byte[] key) 
throws AdbcException;
 
-  static native double connectionGetOptionDouble(long handle, String key) 
throws AdbcException;
+  static native double connectionGetOptionDouble(long handle, byte[] key) 
throws AdbcException;
 
-  static native long connectionGetOptionLong(long handle, String key) throws 
AdbcException;
+  static native long connectionGetOptionLong(long handle, byte[] key) throws 
AdbcException;
 
-  static native String connectionGetOptionString(long handle, String key) 
throws AdbcException;
+  static native byte[] connectionGetOptionString(long handle, byte[] key) 
throws AdbcException;
 
-  static native void connectionSetOptionBytes(long handle, String key, byte[] 
value)
+  static native void connectionSetOptionBytes(long handle, byte[] key, byte[] 
value)
       throws AdbcException;
 
-  static native void connectionSetOptionDouble(long handle, String key, double 
value)
+  static native void connectionSetOptionDouble(long handle, byte[] key, double 
value)
       throws AdbcException;
 
-  static native void connectionSetOptionLong(long handle, String key, long 
value)
+  static native void connectionSetOptionLong(long handle, byte[] key, long 
value)
       throws AdbcException;
 
-  static native void connectionSetOptionString(long handle, String key, String 
value)
+  static native void connectionSetOptionString(long handle, byte[] key, byte[] 
value)
       throws AdbcException;
 
   static native NativeQueryResult connectionGetStatisticNames(long handle) 
throws AdbcException;
 
   static native NativeQueryResult connectionGetStatistics(
       long handle,
-      String catalogPattern,
-      String dbSchemaPattern,
-      String tableNamePattern,
+      byte[] catalogPattern,
+      byte[] dbSchemaPattern,
+      byte[] tableNamePattern,
       boolean approximate)
       throws AdbcException;
 
-  static native byte[] databaseGetOptionBytes(long handle, String key) throws 
AdbcException;
+  static native byte[] databaseGetOptionBytes(long handle, byte[] key) throws 
AdbcException;
 
-  static native double databaseGetOptionDouble(long handle, String key) throws 
AdbcException;
+  static native double databaseGetOptionDouble(long handle, byte[] key) throws 
AdbcException;
 
-  static native long databaseGetOptionLong(long handle, String key) throws 
AdbcException;
+  static native long databaseGetOptionLong(long handle, byte[] key) throws 
AdbcException;
 
-  static native String databaseGetOptionString(long handle, String key) throws 
AdbcException;
+  static native byte[] databaseGetOptionString(long handle, byte[] key) throws 
AdbcException;
 
-  static native void databaseSetOptionBytes(long handle, String key, byte[] 
value)
+  static native void databaseSetOptionBytes(long handle, byte[] key, byte[] 
value)
       throws AdbcException;
 
-  static native void databaseSetOptionDouble(long handle, String key, double 
value)
+  static native void databaseSetOptionDouble(long handle, byte[] key, double 
value)
       throws AdbcException;
 
-  static native void databaseSetOptionLong(long handle, String key, long value)
+  static native void databaseSetOptionLong(long handle, byte[] key, long value)
       throws AdbcException;
 
-  static native void databaseSetOptionString(long handle, String key, String 
value)
+  static native void databaseSetOptionString(long handle, byte[] key, byte[] 
value)
       throws AdbcException;
 }
diff --git 
a/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/JniDriverTest.java
 
b/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/JniDriverTest.java
index 14adf62e0..94712c86f 100644
--- 
a/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/JniDriverTest.java
+++ 
b/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/JniDriverTest.java
@@ -144,6 +144,29 @@ class JniDriverTest {
     }
   }
 
+  // Ensure strings with characters that differ between UTF-8 and Java's 
"modifiefd UTF-8" are
+  // properly serialized
+  @Test
+  void queryNonBmpUtf8() throws Exception {
+    try (final BufferAllocator allocator = new RootAllocator()) {
+      JniDriver driver = new JniDriver(allocator);
+      Map<String, Object> parameters = new HashMap<>();
+      JniDriver.PARAM_DRIVER.set(parameters, "adbc_driver_sqlite");
+      String expected = "\uD83D\uDE00";
+
+      try (final AdbcDatabase db = driver.open(parameters);
+          final AdbcConnection conn = db.connect();
+          final AdbcStatement stmt = conn.createStatement()) {
+        stmt.setSqlQuery("SELECT '" + expected + "'");
+        try (final AdbcStatement.QueryResult result = stmt.executeQuery()) {
+          assertThat(result.getReader().loadNextBatch()).isTrue();
+          
assertThat(result.getReader().getVectorSchemaRoot().getVector(0).getObject(0))
+              .hasToString(expected);
+        }
+      }
+    }
+  }
+
   @Test
   void statementThrowsAfterClose() throws Exception {
     try (final BufferAllocator allocator = new RootAllocator()) {

Reply via email to