diff --git a/implementation/BUILD b/implementation/BUILD
index 6cf4653f..32f82380 100644
--- a/implementation/BUILD
+++ b/implementation/BUILD
@@ -1074,7 +1074,7 @@ cc_test(
     deps = [
         "//:jni_bind",
         "//:jni_test",
-        "//metaprogramming:concatenate",
+        "//metaprogramming:type_to_type_map",
         "@googletest//:gtest_main",
     ],
 )
diff --git a/implementation/array_test.cc b/implementation/array_test.cc
index cc9858a7..025a3dd2 100644
--- a/implementation/array_test.cc
+++ b/implementation/array_test.cc
@@ -16,10 +16,7 @@
 
 #include <type_traits>
 
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
 #include "jni_bind.h"
-#include "jni_test.h"
 
 namespace {
 
diff --git a/implementation/array_type_conversion_test.cc b/implementation/array_type_conversion_test.cc
index eb1084cd..731ff168 100644
--- a/implementation/array_type_conversion_test.cc
+++ b/implementation/array_type_conversion_test.cc
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
+#include <type_traits>
+
 #include "jni_bind.h"
 
 namespace {
diff --git a/implementation/array_view_test.cc b/implementation/array_view_test.cc
index 8f1ff7ab..66496d2f 100644
--- a/implementation/array_view_test.cc
+++ b/implementation/array_view_test.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 #include <algorithm>
+#include <array>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -50,51 +51,43 @@ TEST_F(JniTest, ArrayView_CallsLengthProperly) {
 TEST_F(JniTest, ArrayView_GetsAndReleaseArrayBuffer) {
   EXPECT_CALL(*env_, GetBooleanArrayElements(Eq(Fake<jbooleanArray>()), _))
       .WillOnce(::testing::Return(Fake<jboolean*>()));
-  EXPECT_CALL(*env_, ReleaseBooleanArrayElements(
-                         Eq(Fake<jbooleanArray>()),
-                         Eq(Fake<jboolean*>()), 0));
+  EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Eq(Fake<jbooleanArray>()),
+                                                 Eq(Fake<jboolean*>()), 0));
 
   EXPECT_CALL(*env_, GetByteArrayElements(Eq(Fake<jbyteArray>()), _))
       .WillOnce(::testing::Return(Fake<jbyte*>()));
-  EXPECT_CALL(*env_,
-              ReleaseByteArrayElements(Eq(Fake<jbyteArray>()),
-                                       Eq(Fake<jbyte*>()), 0));
+  EXPECT_CALL(*env_, ReleaseByteArrayElements(Eq(Fake<jbyteArray>()),
+                                              Eq(Fake<jbyte*>()), 0));
 
   EXPECT_CALL(*env_, GetCharArrayElements(Eq(Fake<jcharArray>()), _))
       .WillOnce(::testing::Return(Fake<jchar*>()));
-  EXPECT_CALL(*env_,
-              ReleaseCharArrayElements(Eq(Fake<jcharArray>()),
-                                       Eq(Fake<jchar*>()), 0));
+  EXPECT_CALL(*env_, ReleaseCharArrayElements(Eq(Fake<jcharArray>()),
+                                              Eq(Fake<jchar*>()), 0));
 
   EXPECT_CALL(*env_, GetShortArrayElements(Eq(Fake<jshortArray>()), _))
       .WillOnce(::testing::Return(Fake<jshort*>()));
-  EXPECT_CALL(
-      *env_, ReleaseShortArrayElements(Eq(Fake<jshortArray>()),
-                                       Eq(Fake<jshort*>()), 0));
+  EXPECT_CALL(*env_, ReleaseShortArrayElements(Eq(Fake<jshortArray>()),
+                                               Eq(Fake<jshort*>()), 0));
 
   EXPECT_CALL(*env_, GetIntArrayElements(Eq(Fake<jintArray>()), _))
       .WillOnce(::testing::Return(Fake<jint*>()));
-  EXPECT_CALL(*env_,
-              ReleaseIntArrayElements(Eq(Fake<jintArray>()),
-                                      Eq(Fake<jint*>()), 0));
+  EXPECT_CALL(*env_, ReleaseIntArrayElements(Eq(Fake<jintArray>()),
+                                             Eq(Fake<jint*>()), 0));
 
   EXPECT_CALL(*env_, GetLongArrayElements(Eq(Fake<jlongArray>()), _))
       .WillOnce(::testing::Return(Fake<jlong*>()));
-  EXPECT_CALL(*env_,
-              ReleaseLongArrayElements(Eq(Fake<jlongArray>()),
-                                       Eq(Fake<jlong*>()), 0));
+  EXPECT_CALL(*env_, ReleaseLongArrayElements(Eq(Fake<jlongArray>()),
+                                              Eq(Fake<jlong*>()), 0));
 
   EXPECT_CALL(*env_, GetFloatArrayElements(Eq(Fake<jfloatArray>()), _))
       .WillOnce(::testing::Return(Fake<jfloat*>()));
-  EXPECT_CALL(
-      *env_, ReleaseFloatArrayElements(Eq(Fake<jfloatArray>()),
-                                       Eq(Fake<jfloat*>()), 0));
+  EXPECT_CALL(*env_, ReleaseFloatArrayElements(Eq(Fake<jfloatArray>()),
+                                               Eq(Fake<jfloat*>()), 0));
 
   EXPECT_CALL(*env_, GetDoubleArrayElements(Eq(Fake<jdoubleArray>()), _))
       .WillOnce(::testing::Return(Fake<jdouble*>()));
-  EXPECT_CALL(*env_, ReleaseDoubleArrayElements(
-                         Eq(Fake<jdoubleArray>()),
-                         Eq(Fake<jdouble*>()), 0));
+  EXPECT_CALL(*env_, ReleaseDoubleArrayElements(Eq(Fake<jdoubleArray>()),
+                                                Eq(Fake<jdouble*>()), 0));
 
   LocalArray<jboolean> boolean_array{AdoptLocal{}, Fake<jbooleanArray>()};
   LocalArray<jbyte> byte_array{AdoptLocal{}, Fake<jbyteArray>()};
@@ -118,9 +111,8 @@ TEST_F(JniTest, ArrayView_GetsAndReleaseArrayBuffer) {
 TEST_F(JniTest, LocalArrayView_AllowsCTAD) {
   EXPECT_CALL(*env_, GetBooleanArrayElements(Eq(Fake<jbooleanArray>()), _))
       .WillOnce(::testing::Return(Fake<jboolean*>()));
-  EXPECT_CALL(*env_, ReleaseBooleanArrayElements(
-                         Eq(Fake<jbooleanArray>()),
-                         Eq(Fake<jboolean*>()), 0));
+  EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Eq(Fake<jbooleanArray>()),
+                                                 Eq(Fake<jboolean*>()), 0));
 
   LocalArray<jboolean> boolean_array{AdoptLocal{}, Fake<jbooleanArray>()};
   ArrayView ctad_array_view{boolean_array.Pin()};
diff --git a/implementation/class_loader_ref_test.cc b/implementation/class_loader_ref_test.cc
index 3868736e..66388550 100644
--- a/implementation/class_loader_ref_test.cc
+++ b/implementation/class_loader_ref_test.cc
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include <optional>
 #include <utility>
 
 #include <gmock/gmock.h>
diff --git a/implementation/class_loader_test.cc b/implementation/class_loader_test.cc
index 8ca0d6fe..29b3962c 100644
--- a/implementation/class_loader_test.cc
+++ b/implementation/class_loader_test.cc
@@ -13,9 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include <gtest/gtest.h>
 #include "jni_bind.h"
-#include "jni_test.h"
 
 namespace {
 
diff --git a/implementation/class_test.cc b/implementation/class_test.cc
index ec490a2f..44bb0fbb 100644
--- a/implementation/class_test.cc
+++ b/implementation/class_test.cc
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include "jni_bind.h"
 #include "jni_test.h"
diff --git a/implementation/constructor_test.cc b/implementation/constructor_test.cc
index 4e52042d..c51bb097 100644
--- a/implementation/constructor_test.cc
+++ b/implementation/constructor_test.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include "jni_bind.h"
 #include "jni_test.h"
diff --git a/implementation/field_ref_test.cc b/implementation/field_ref_test.cc
index a6bf521a..1aa5244a 100644
--- a/implementation/field_ref_test.cc
+++ b/implementation/field_ref_test.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include <optional>
+#include <utility>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
diff --git a/implementation/global_object_test.cc b/implementation/global_object_test.cc
index 7e19f79d..3d5af1df 100644
--- a/implementation/global_object_test.cc
+++ b/implementation/global_object_test.cc
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include <optional>
+#include <tuple>
 #include <utility>
 
 #include <gmock/gmock.h>
diff --git a/implementation/id_test.cc b/implementation/id_test.cc
index b5826a7f..979fb1bf 100644
--- a/implementation/id_test.cc
+++ b/implementation/id_test.cc
@@ -16,8 +16,6 @@
 
 #include <string_view>
 
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
 #include "jni_bind.h"
 
 namespace {
diff --git a/implementation/jni_type_test.cc b/implementation/jni_type_test.cc
index df2d2d7f..8c1b55b4 100644
--- a/implementation/jni_type_test.cc
+++ b/implementation/jni_type_test.cc
@@ -15,8 +15,6 @@
  */
 #include <type_traits>
 
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
 #include "jni_bind.h"
 
 namespace {
diff --git a/implementation/jvm_test.cc b/implementation/jvm_test.cc
index 0cfed6fe..66008e7e 100644
--- a/implementation/jvm_test.cc
+++ b/implementation/jvm_test.cc
@@ -14,14 +14,8 @@
  * limitations under the License.
  */
 
-#include <thread>
-
-#include <gmock/gmock.h>
 #include <gtest/gtest.h>
-#include "implementation/jni_helper/jni_env.h"
-#include "implementation/jni_helper/jni_helper.h"
 #include "jni_bind.h"
-#include "jni_test.h"
 
 namespace {
 
diff --git a/implementation/legacy/BUILD b/implementation/legacy/BUILD
new file mode 100644
index 00000000..76591293
--- /dev/null
+++ b/implementation/legacy/BUILD
@@ -0,0 +1,245 @@
+package(
+    licenses = ["notice"],
+)
+
+################################################################################
+# Array.
+################################################################################
+cc_test(
+    name = "array_view_test",
+    srcs = ["array_view_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+################################################################################
+# ClassLoader.
+################################################################################
+cc_test(
+    name = "class_loader_ref_test",
+    srcs = ["class_loader_ref_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation:configuration",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "class_loader_ref_second_test",
+    srcs = ["class_loader_ref_second_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+################################################################################
+# Field.
+################################################################################
+cc_test(
+    name = "field_ref_test",
+    srcs = ["field_ref_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+################################################################################
+# GlobalObject.
+################################################################################
+cc_test(
+    name = "global_object_test",
+    srcs = ["global_object_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+################################################################################
+# LocalArray.
+################################################################################
+cc_test(
+    name = "local_array_field_multidimensional_test",
+    srcs = ["local_array_field_multidimensional_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "local_array_field_test",
+    srcs = ["local_array_field_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "local_array_iteration_test",
+    srcs = ["local_array_iteration_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "local_array_multidimensional_test",
+    srcs = ["local_array_multidimensional_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "local_array_method_test",
+    srcs = ["local_array_method_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "local_array_method_multidimensional_test",
+    srcs = ["local_array_method_multidimensional_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "local_array_string_test",
+    srcs = ["local_array_string_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+################################################################################
+# LocalObject.
+################################################################################
+cc_test(
+    name = "local_object_test",
+    srcs = ["local_object_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+################################################################################
+# Method.
+################################################################################
+cc_test(
+    name = "method_ref_test",
+    srcs = ["method_ref_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+################################################################################
+# Multi type test.
+################################################################################
+cc_test(
+    name = "multi_type_test",
+    srcs = ["multi_type_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+################################################################################
+# Object.
+################################################################################
+cc_test(
+    name = "overload_ref_test",
+    srcs = ["overload_ref_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper",
+        "@googletest//:gtest_main",
+    ],
+)
+
+################################################################################
+# Static.
+################################################################################
+cc_test(
+    name = "static_ref_test",
+    srcs = ["static_ref_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "string_ref_test",
+    srcs = ["string_ref_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "//implementation/jni_helper:fake_test_constants",
+        "@googletest//:gtest_main",
+    ],
+)
+
+################################################################################
+# Self.
+################################################################################
+cc_test(
+    name = "self_test",
+    srcs = ["self_test.cc"],
+    deps = [
+        "//:jni_bind",
+        "//:jni_test",
+        "@googletest//:gtest_main",
+    ],
+)
diff --git a/implementation/legacy/README.md b/implementation/legacy/README.md
new file mode 100644
index 00000000..1677833d
--- /dev/null
+++ b/implementation/legacy/README.md
@@ -0,0 +1,3 @@
+This directory contains a copy of the tests from implementation but using the legacy `obj.Foo("methodName", args...);` syntax.
+
+These tests are effectively pure duplication and may be removed at some future point. They are here to maintain coverage as JNI Bind migrates to a C++20 friendly syntax.
diff --git a/implementation/legacy/array_view_test.cc b/implementation/legacy/array_view_test.cc
new file mode 100644
index 00000000..7130e16f
--- /dev/null
+++ b/implementation/legacy/array_view_test.cc
@@ -0,0 +1,447 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <algorithm>
+#include <array>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+namespace {
+
+#if __clang__
+
+using ::jni::AdoptLocal;
+using ::jni::ArrayView;
+using ::jni::CDecl_t;
+using ::jni::Class;
+using ::jni::Fake;
+using ::jni::LocalArray;
+using ::jni::LocalObject;
+using ::jni::Method;
+using ::jni::Return;
+using ::jni::test::AsNewLocalReference;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::Eq;
+
+////////////////////////////////////////////////////////////////////////////////
+// Pin Tests.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, ArrayView_CallsLengthProperly) {
+  EXPECT_CALL(*env_, GetArrayLength).WillOnce(::testing::Return(3));
+
+  LocalArray<jint> local_int_array{5};
+  EXPECT_EQ(local_int_array.Length(), 3);
+}
+
+TEST_F(JniTest, ArrayView_GetsAndReleaseArrayBuffer) {
+  EXPECT_CALL(*env_, GetBooleanArrayElements(Eq(Fake<jbooleanArray>()), _))
+      .WillOnce(::testing::Return(Fake<jboolean*>()));
+  EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Eq(Fake<jbooleanArray>()),
+                                                 Eq(Fake<jboolean*>()), 0));
+
+  EXPECT_CALL(*env_, GetByteArrayElements(Eq(Fake<jbyteArray>()), _))
+      .WillOnce(::testing::Return(Fake<jbyte*>()));
+  EXPECT_CALL(*env_, ReleaseByteArrayElements(Eq(Fake<jbyteArray>()),
+                                              Eq(Fake<jbyte*>()), 0));
+
+  EXPECT_CALL(*env_, GetCharArrayElements(Eq(Fake<jcharArray>()), _))
+      .WillOnce(::testing::Return(Fake<jchar*>()));
+  EXPECT_CALL(*env_, ReleaseCharArrayElements(Eq(Fake<jcharArray>()),
+                                              Eq(Fake<jchar*>()), 0));
+
+  EXPECT_CALL(*env_, GetShortArrayElements(Eq(Fake<jshortArray>()), _))
+      .WillOnce(::testing::Return(Fake<jshort*>()));
+  EXPECT_CALL(*env_, ReleaseShortArrayElements(Eq(Fake<jshortArray>()),
+                                               Eq(Fake<jshort*>()), 0));
+
+  EXPECT_CALL(*env_, GetIntArrayElements(Eq(Fake<jintArray>()), _))
+      .WillOnce(::testing::Return(Fake<jint*>()));
+  EXPECT_CALL(*env_, ReleaseIntArrayElements(Eq(Fake<jintArray>()),
+                                             Eq(Fake<jint*>()), 0));
+
+  EXPECT_CALL(*env_, GetLongArrayElements(Eq(Fake<jlongArray>()), _))
+      .WillOnce(::testing::Return(Fake<jlong*>()));
+  EXPECT_CALL(*env_, ReleaseLongArrayElements(Eq(Fake<jlongArray>()),
+                                              Eq(Fake<jlong*>()), 0));
+
+  EXPECT_CALL(*env_, GetFloatArrayElements(Eq(Fake<jfloatArray>()), _))
+      .WillOnce(::testing::Return(Fake<jfloat*>()));
+  EXPECT_CALL(*env_, ReleaseFloatArrayElements(Eq(Fake<jfloatArray>()),
+                                               Eq(Fake<jfloat*>()), 0));
+
+  EXPECT_CALL(*env_, GetDoubleArrayElements(Eq(Fake<jdoubleArray>()), _))
+      .WillOnce(::testing::Return(Fake<jdouble*>()));
+  EXPECT_CALL(*env_, ReleaseDoubleArrayElements(Eq(Fake<jdoubleArray>()),
+                                                Eq(Fake<jdouble*>()), 0));
+
+  LocalArray<jboolean> boolean_array{AdoptLocal{}, Fake<jbooleanArray>()};
+  LocalArray<jbyte> byte_array{AdoptLocal{}, Fake<jbyteArray>()};
+  LocalArray<jchar> char_array{AdoptLocal{}, Fake<jcharArray>()};
+  LocalArray<jshort> short_array{AdoptLocal{}, Fake<jshortArray>()};
+  LocalArray<jint> int_array{AdoptLocal{}, Fake<jintArray>()};
+  LocalArray<jlong> long_array{AdoptLocal{}, Fake<jlongArray>()};
+  LocalArray<jfloat> float_array{AdoptLocal{}, Fake<jfloatArray>()};
+  LocalArray<jdouble> double_array{AdoptLocal{}, Fake<jdoubleArray>()};
+
+  ArrayView<jboolean, 1> boolean_array_pin = {boolean_array.Pin()};
+  ArrayView<jbyte, 1> byte_array_pin = {byte_array.Pin()};
+  ArrayView<jint, 1> int_array_pin = {int_array.Pin()};
+  ArrayView<jchar, 1> char_array_pin = {char_array.Pin()};
+  ArrayView<jshort, 1> short_array_pin = {short_array.Pin()};
+  ArrayView<jlong, 1> long_array_pin = {long_array.Pin()};
+  ArrayView<jfloat, 1> float_array_pin = {float_array.Pin()};
+  ArrayView<jdouble, 1> double_array_pin = {double_array.Pin()};
+}
+
+TEST_F(JniTest, LocalArrayView_AllowsCTAD) {
+  EXPECT_CALL(*env_, GetBooleanArrayElements(Eq(Fake<jbooleanArray>()), _))
+      .WillOnce(::testing::Return(Fake<jboolean*>()));
+  EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Eq(Fake<jbooleanArray>()),
+                                                 Eq(Fake<jboolean*>()), 0));
+
+  LocalArray<jboolean> boolean_array{AdoptLocal{}, Fake<jbooleanArray>()};
+  ArrayView ctad_array_view{boolean_array.Pin()};
+
+  // Despite supporting construction from xvalue, move ctor is deleted (good).
+  // ArrayView ctad_array_view_2 {std::move(ctad_array_view)};
+}
+
+TEST_F(JniTest, ArrayView_ConstructsFromAnObject) {
+  static constexpr Class kClass{"kClass"};
+  LocalArray<jobject, 1, kClass> local_obj_array{1, LocalObject<kClass>{}};
+}
+
+TEST_F(JniTest, ArrayView_ConstructsFromAnObjectRValueWithCTAD) {
+  static constexpr Class kClass{"kClass"};
+  LocalArray<jobject, 1, kClass> local_obj_array{1, LocalObject<kClass>{}};
+}
+
+TEST_F(JniTest, ArrayView_GetsAnObject) {
+  static constexpr Class kClass{"kClass"};
+
+  EXPECT_CALL(*env_, GetObjectArrayElement(_, _));
+  LocalArray<jobject, 1, kClass> local_obj_array{1, LocalObject<kClass>{}};
+  local_obj_array.Get(0);
+}
+
+TEST_F(JniTest, ArrayView_GetsAnObjectWithCTAD) {
+  static constexpr Class kClass{"kClass"};
+
+  EXPECT_CALL(*env_, GetObjectArrayElement(_, _));
+  LocalArray<jobject, 1, kClass> local_obj_array{1, LocalObject<kClass>{}};
+  local_obj_array.Get(0);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Iteration Tests: Primitives.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, ArrayView_BooleanIsIterable) {
+  std::array fake_vals{jboolean{true}, jboolean{false}, jboolean{true}};
+  EXPECT_CALL(*env_, NewBooleanArray(3))
+      .WillOnce(::testing::Return(Fake<jbooleanArray>()));
+  EXPECT_CALL(*env_, GetArrayLength(Fake<jbooleanArray>()))
+      .WillOnce(::testing::Return(3));
+  EXPECT_CALL(*env_, GetBooleanArrayElements)
+      .WillOnce(::testing::Return(fake_vals.data()));
+
+  LocalArray<jboolean> bool_arr{3};
+  ArrayView<jboolean, 1> bool_view = bool_arr.Pin();
+
+  EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(),
+                         fake_vals.end()));
+}
+
+TEST_F(JniTest, ArrayView_ByteIsIterable) {
+  std::array fake_vals{jbyte{true}, jbyte{false}, jbyte{true}};
+  EXPECT_CALL(*env_, NewByteArray(3))
+      .WillOnce(::testing::Return(Fake<jbyteArray>()));
+  EXPECT_CALL(*env_, GetArrayLength(Fake<jbyteArray>()))
+      .WillOnce(testing::Return(3));
+  EXPECT_CALL(*env_, GetByteArrayElements)
+      .WillOnce(::testing::Return(fake_vals.data()));
+
+  LocalArray<jbyte> bool_arr{3};
+  ArrayView<jbyte, 1> bool_view = bool_arr.Pin();
+
+  EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(),
+                         fake_vals.end()));
+}
+
+TEST_F(JniTest, ArrayView_CharIsIterable) {
+  std::array fake_vals{jchar{true}, jchar{false}, jchar{true}};
+  EXPECT_CALL(*env_, NewCharArray(3))
+      .WillOnce(::testing::Return(Fake<jcharArray>()));
+  EXPECT_CALL(*env_, GetArrayLength(Fake<jcharArray>()))
+      .WillOnce(::testing::Return(3));
+  EXPECT_CALL(*env_, GetCharArrayElements)
+      .WillOnce(::testing::Return(fake_vals.data()));
+
+  LocalArray<jchar> bool_arr{3};
+  ArrayView<jchar, 1> bool_view = bool_arr.Pin();
+
+  EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(),
+                         fake_vals.end()));
+}
+
+TEST_F(JniTest, ArrayView_ShortIsIterable) {
+  std::array fake_vals{jshort{true}, jshort{false}, jshort{true}};
+  EXPECT_CALL(*env_, NewShortArray(3))
+      .WillOnce(::testing::Return(Fake<jshortArray>()));
+  EXPECT_CALL(*env_, GetArrayLength(Fake<jshortArray>()))
+      .WillOnce(::testing::Return(3));
+  EXPECT_CALL(*env_, GetShortArrayElements)
+      .WillOnce(::testing::Return(fake_vals.data()));
+
+  LocalArray<jshort> bool_arr{3};
+  ArrayView<jshort, 1> bool_view = bool_arr.Pin();
+
+  EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(),
+                         fake_vals.end()));
+}
+
+TEST_F(JniTest, ArrayView_IntIsIterable) {
+  std::array fake_vals{jint{1}, jint{2}, jint{3}};
+
+  EXPECT_CALL(*env_, NewIntArray(3))
+      .WillOnce(::testing::Return(Fake<jintArray>()));
+  EXPECT_CALL(*env_, GetArrayLength(Fake<jintArray>()))
+      .WillOnce(::testing::Return(3));
+  EXPECT_CALL(*env_, GetIntArrayElements)
+      .WillOnce(::testing::Return(fake_vals.data()));
+
+  LocalArray<jint> int_arr{3};
+  ArrayView<jint, 1> int_view = int_arr.Pin();
+
+  EXPECT_TRUE(std::equal(int_view.begin(), int_view.end(), fake_vals.begin(),
+                         fake_vals.end()));
+}
+
+TEST_F(JniTest, ArrayView_LongIsIterable) {
+  std::array fake_vals{jlong{true}, jlong{false}, jlong{true}};
+  EXPECT_CALL(*env_, NewLongArray(3))
+      .WillOnce(::testing::Return(Fake<jlongArray>()));
+  EXPECT_CALL(*env_, GetArrayLength(Fake<jlongArray>()))
+      .WillOnce(::testing::Return(3));
+  EXPECT_CALL(*env_, GetLongArrayElements)
+      .WillOnce(::testing::Return(fake_vals.data()));
+
+  LocalArray<jlong> bool_arr{3};
+  ArrayView<jlong, 1> bool_view = bool_arr.Pin();
+
+  EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(),
+                         fake_vals.end()));
+}
+
+TEST_F(JniTest, ArrayView_FloatIsIterable) {
+  std::array fake_vals{jfloat{true}, jfloat{false}, jfloat{true}};
+
+  EXPECT_CALL(*env_, NewFloatArray(3))
+      .WillOnce(::testing::Return(Fake<jfloatArray>()));
+  EXPECT_CALL(*env_, GetArrayLength(Fake<jfloatArray>()))
+      .WillOnce(::testing::Return(3));
+  EXPECT_CALL(*env_, GetFloatArrayElements)
+      .WillOnce(::testing::Return(fake_vals.data()));
+
+  LocalArray<jfloat> bool_arr{3};
+  ArrayView<jfloat, 1> bool_view = bool_arr.Pin();
+
+  EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(),
+                         fake_vals.end()));
+}
+
+TEST_F(JniTest, ArrayView_DoubleIsIterable) {
+  std::array fake_vals{jdouble{true}, jdouble{false}, jdouble{true}};
+  EXPECT_CALL(*env_, NewDoubleArray(3))
+      .WillOnce(::testing::Return(Fake<jdoubleArray>()));
+  EXPECT_CALL(*env_, GetArrayLength(Fake<jdoubleArray>()))
+      .WillOnce(::testing::Return(3));
+  EXPECT_CALL(*env_, GetDoubleArrayElements)
+      .WillOnce(::testing::Return(fake_vals.data()));
+
+  LocalArray<jdouble> bool_arr{3};
+  ArrayView<jdouble, 1> bool_view = bool_arr.Pin();
+
+  EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(),
+                         fake_vals.end()));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Iteration Tests: Objects.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, ArrayView_ShallowObjectsAreIterable) {
+  std::array fake_vals{Fake<jobject>(1), Fake<jobject>(2), Fake<jobject>(3)};
+
+  EXPECT_CALL(*env_, GetArrayLength(Fake<jobjectArray>()))
+      .WillOnce(::testing::Return(3));
+  EXPECT_CALL(*env_, GetObjectArrayElement)
+      .WillOnce(::testing::Return(Fake<jobject>(1)))
+      .WillOnce(::testing::Return(Fake<jobject>(2)))
+      .WillOnce(::testing::Return(Fake<jobject>(3)));
+
+  LocalArray<jobject> obj_arr{AdoptLocal{}, Fake<jobjectArray>()};
+  ArrayView<jobject, 1> obj_view = obj_arr.Pin();
+
+  EXPECT_TRUE(std::equal(obj_view.begin(), obj_view.end(), fake_vals.begin(),
+                         fake_vals.end()));
+}
+
+TEST_F(JniTest, ArrayView_RichObjectsAreIterable) {
+  static constexpr Class kClass{"kClass", Method{"Foo", Return<int>{}}};
+
+  EXPECT_CALL(*env_, GetArrayLength(Fake<jobjectArray>()))
+      .WillOnce(testing::Return(3));
+  EXPECT_CALL(*env_, GetObjectArrayElement)
+      .WillOnce(::testing::Return(Fake<jobject>(1)))
+      .WillOnce(::testing::Return(Fake<jobject>(2)))
+      .WillOnce(::testing::Return(Fake<jobject>(3)));
+
+  LocalArray<jobject, 1, kClass> obj_arr{AdoptLocal{}, Fake<jobjectArray>()};
+  auto obj_view = obj_arr.Pin();
+
+  // Note: GlobalObject will fail to compile here. This is good, the user
+  // should be forced to explicitly promote the local.
+  int fake_result = 123;
+  for (LocalObject<kClass> obj : obj_view) {
+    EXPECT_CALL(*env_, CallIntMethodV).WillOnce(::testing::Return(fake_result));
+    EXPECT_EQ(obj("Foo"), fake_result);
+    fake_result++;
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Iteration Tests: Rank 2 Iterations.
+//
+// Note: Writing through every type would be tedious, however, if these tests
+//   could be generalised across the universe of types it would be better.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, ArrayView_Rank2IntArraysAreIterable) {
+  std::array fake_vals{Fake<jintArray>(1), Fake<jintArray>(2),
+                       Fake<jintArray>(3)};
+
+  EXPECT_CALL(*env_, GetArrayLength(Fake<jobjectArray>()))
+      .WillOnce(::testing::Return(3));
+  EXPECT_CALL(*env_, GetObjectArrayElement(
+                         AsNewLocalReference(Fake<jobjectArray>()), 0))
+      .WillOnce(::testing::Return(Fake<jintArray>(1)));
+  EXPECT_CALL(*env_, GetObjectArrayElement(
+                         AsNewLocalReference(Fake<jobjectArray>()), 1))
+      .WillOnce(::testing::Return(Fake<jintArray>(2)));
+  EXPECT_CALL(*env_, GetObjectArrayElement(
+                         AsNewLocalReference(Fake<jobjectArray>()), 2))
+      .WillOnce(::testing::Return(Fake<jintArray>(3)));
+
+  LocalArray<jint, 2> int_arr_rank_2{AdoptLocal{}, Fake<jobjectArray>()};
+  ArrayView<jint, 2> int_rank2_view = int_arr_rank_2.Pin();
+
+  EXPECT_TRUE(std::equal(int_rank2_view.begin(), int_rank2_view.end(),
+                         fake_vals.begin(), fake_vals.end()));
+
+  /*
+  // Also viable to write this:
+  // for (LocalArray<jint, 1> jint_array : int_rank2_view) { }
+  */
+}
+
+TEST_F(JniTest, ArrayView_Rank2ObjectkArraysAreIterable) {
+  std::array fake_vals{Fake<jobjectArray>(1), Fake<jobjectArray>(2),
+                       Fake<jobjectArray>(3)};
+
+  EXPECT_CALL(*env_, GetArrayLength(Fake<jobjectArray>()))
+      .WillOnce(::testing::Return(3));
+  EXPECT_CALL(*env_, GetObjectArrayElement(
+                         AsNewLocalReference(Fake<jobjectArray>()), 0))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(1)));
+  EXPECT_CALL(*env_, GetObjectArrayElement(
+                         AsNewLocalReference(Fake<jobjectArray>()), 1))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(2)));
+  EXPECT_CALL(*env_, GetObjectArrayElement(
+                         AsNewLocalReference(Fake<jobjectArray>()), 2))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(3)));
+
+  LocalArray<jobject, 2> int_arr_rank_2{AdoptLocal{}, Fake<jobjectArray>()};
+  ArrayView<jobject, 2> int_rank2_view = int_arr_rank_2.Pin();
+
+  EXPECT_TRUE(std::equal(int_rank2_view.begin(), int_rank2_view.end(),
+                         fake_vals.begin(), fake_vals.end()));
+
+  /*
+  // Also viable to write this:
+  // for (LocalArray<jint, 1> jint_array : int_rank2_view) { }
+  */
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Iteration Tests: Rank 3 Iterations.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, ArrayView_Rank3IntArraysAreIterable) {
+  std::array fake_vals{Fake<jobjectArray>(), Fake<jobjectArray>(),
+                       Fake<jobjectArray>()};
+
+  EXPECT_CALL(*env_, GetArrayLength(Fake<jobjectArray>()))
+      .WillOnce(::testing::Return(3));
+  EXPECT_CALL(*env_, GetObjectArrayElement(
+                         AsNewLocalReference(Fake<jobjectArray>()), 0))
+      .WillOnce(::testing::Return(Fake<jobjectArray>()));
+  EXPECT_CALL(*env_, GetObjectArrayElement(
+                         AsNewLocalReference(Fake<jobjectArray>()), 1))
+      .WillOnce(::testing::Return(Fake<jobjectArray>()));
+  EXPECT_CALL(*env_, GetObjectArrayElement(
+                         AsNewLocalReference(Fake<jobjectArray>()), 2))
+      .WillOnce(::testing::Return(Fake<jobjectArray>()));
+
+  LocalArray<jint, 3> int_arr_rank_3{AdoptLocal{}, Fake<jobjectArray>()};
+  ArrayView<jint, 3> int_rank_3_view = int_arr_rank_3.Pin();
+
+  EXPECT_TRUE(std::equal(int_rank_3_view.begin(), int_rank_3_view.end(),
+                         fake_vals.begin(), fake_vals.end()));
+
+  // Also viable to write this:
+  // for (LocalArray<jint, 1> jint_array : int_rank_3_view) {  }
+}
+
+TEST_F(JniTest, ArrayView_Rank3ObjectkArraysAreIterable) {
+  std::array fake_vals{Fake<jobjectArray>(1), Fake<jobjectArray>(2),
+                       Fake<jobjectArray>(3)};
+
+  EXPECT_CALL(*env_, GetArrayLength(Fake<jobjectArray>(0)))
+      .WillOnce(::testing::Return(3));
+  EXPECT_CALL(*env_, GetObjectArrayElement(
+                         AsNewLocalReference(Fake<jobjectArray>()), 0))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(1)));
+  EXPECT_CALL(*env_, GetObjectArrayElement(
+                         AsNewLocalReference(Fake<jobjectArray>()), 1))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(2)));
+  EXPECT_CALL(*env_, GetObjectArrayElement(
+                         AsNewLocalReference(Fake<jobjectArray>()), 2))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(3)));
+
+  LocalArray<jobject, 3> object_arr_rank_3{AdoptLocal{}, Fake<jobjectArray>(0)};
+  ArrayView<jobject, 3> object_rank_3_view = object_arr_rank_3.Pin();
+
+  EXPECT_TRUE(std::equal(object_rank_3_view.begin(), object_rank_3_view.end(),
+                         fake_vals.begin(), fake_vals.end()));
+
+  // Also viable to write this:
+  // for (LocalArray<jobject, 2> jobject_array : object_rank_3_view) { }
+}
+
+#endif  // __clang__
+
+}  // namespace
diff --git a/implementation/legacy/class_loader_ref_second_test.cc b/implementation/legacy/class_loader_ref_second_test.cc
new file mode 100644
index 00000000..2641126f
--- /dev/null
+++ b/implementation/legacy/class_loader_ref_second_test.cc
@@ -0,0 +1,103 @@
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+namespace {
+
+#if __clang__
+
+using ::jni::AdoptLocal;
+using ::jni::Class;
+using ::jni::ClassLoader;
+using ::jni::Constructor;
+using ::jni::Fake;
+using ::jni::Jvm;
+using ::jni::JvmRef;
+using ::jni::kNullClassLoader;
+using ::jni::LocalClassLoader;
+using ::jni::Method;
+using ::jni::Params;
+using ::jni::Return;
+using ::jni::SupportedClassSet;
+using ::jni::test::AsGlobal;
+using ::jni::test::JniTestWithNoDefaultJvmRef;
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::StrEq;
+
+// This test is isolated to correctly observe querying to Class + ClassLoader
+// class definitions. Ids are now cached statically against their name, so this
+// results in crosstalk.
+TEST_F(JniTestWithNoDefaultJvmRef,
+       ClassLoaderRefTest_ClassLoadersDoNotConflict) {
+  static constexpr Class kClass{
+      "com/google/kClass", Constructor<jint>{},
+      Method{"methodNoCrossTalk", Return<jint>{}, Params<jint>{}}};
+  static constexpr ClassLoader kClassLoader{kNullClassLoader,
+                                            SupportedClassSet{kClass}};
+
+  // We will use this ClassLoader instead of the default loader to load
+  // Class.
+  static constexpr Jvm kClassLoaderJvm{kClassLoader};
+
+  InSequence sequence;
+
+  EXPECT_CALL(*env_, FindClass(StrEq("java/lang/ClassLoader")))
+      .WillOnce(testing::Return(Fake<jclass>(1)));
+
+  EXPECT_CALL(*env_, NewGlobalRef(Fake<jclass>(1)))
+      .WillOnce(testing::Return(AsGlobal(Fake<jclass>(1))));
+
+  EXPECT_CALL(*env_,
+              GetMethodID(AsGlobal(Fake<jclass>(1)), StrEq("loadClass"),
+                          StrEq("(Ljava/lang/String;)Ljava/lang/Class;")))
+      .WillOnce(testing::Return(Fake<jmethodID>(1)));
+
+  EXPECT_CALL(*env_, NewStringUTF(_))
+      .WillOnce(testing::Return(Fake<jstring>()));
+
+  // We should only try to load the class once even if we create multiple
+  // instances.
+  EXPECT_CALL(*env_, CallObjectMethodV(Fake<jobject>(1), Fake<jmethodID>(1), _))
+      .WillOnce(testing::Return(Fake<jclass>(2)));
+
+  EXPECT_CALL(*env_, NewGlobalRef(Fake<jclass>(2)))
+      .WillOnce(testing::Return(AsGlobal(Fake<jclass>(2))));
+  EXPECT_CALL(*env_, GetMethodID(AsGlobal(Fake<jclass>(2)), StrEq("<init>"),
+                                 StrEq("(I)V")))
+      .WillOnce(testing::Return(Fake<jmethodID>(2)));
+  EXPECT_CALL(*env_,
+              NewObjectV(AsGlobal(Fake<jclass>(2)), Fake<jmethodID>(2), _))
+      .WillOnce(testing::Return(Fake<jobject>(2)));
+  EXPECT_CALL(*env_,
+              NewObjectV(AsGlobal(Fake<jclass>(2)), Fake<jmethodID>(2), _))
+      .WillOnce(testing::Return(Fake<jobject>(3)));
+
+  EXPECT_CALL(*env_,
+              GetMethodID(AsGlobal(Fake<jclass>(2)), StrEq("methodNoCrossTalk"),
+
+                          StrEq("(I)I")))
+      .WillOnce(testing::Return(Fake<jmethodID>(3)));
+  EXPECT_CALL(*env_, CallIntMethodV(Fake<jobject>(2), Fake<jmethodID>(3), _))
+      .WillOnce(testing::Return(123));
+
+  // Code under test.
+  JvmRef<kClassLoaderJvm> jvm_ref{jvm_.get()};
+  LocalClassLoader<kClassLoader, kClassLoaderJvm> class_loader{
+      AdoptLocal{}, Fake<jobject>(1)};
+
+  auto custom_loader_object = class_loader.BuildLocalObject<kClass>(jint{1});
+
+  auto second_custom_loader_object =
+      class_loader.BuildLocalObject<kClass>(jint{2});
+
+  EXPECT_EQ(custom_loader_object("methodNoCrossTalk", jint{2}), 123);
+
+  TearDown();
+}
+
+#endif  // __clang__
+
+}  // namespace
diff --git a/implementation/legacy/class_loader_ref_test.cc b/implementation/legacy/class_loader_ref_test.cc
new file mode 100644
index 00000000..64e73e17
--- /dev/null
+++ b/implementation/legacy/class_loader_ref_test.cc
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <utility>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "jni_bind.h"
+#include "jni_test.h"
+
+namespace {
+
+#if __clang__
+
+using ::jni::AdoptGlobal;
+using ::jni::AdoptLocal;
+using ::jni::Class;
+using ::jni::ClassLoader;
+using ::jni::Constructor;
+using ::jni::Fake;
+using ::jni::GlobalClassLoader;
+using ::jni::Jvm;
+using ::jni::JvmRef;
+using ::jni::kDefaultJvm;
+using ::jni::kNullClassLoader;
+using ::jni::LocalClassLoader;
+using ::jni::LocalObject;
+using ::jni::Method;
+using ::jni::Params;
+using ::jni::PromoteToGlobal;
+using ::jni::Return;
+using ::jni::SupportedClassSet;
+using ::jni::test::AsGlobal;
+using ::jni::test::JniTest;
+using ::jni::test::JniTestWithNoDefaultJvmRef;
+using ::jni::test::kDefaultConfiguration;
+using ::testing::_;
+using ::testing::Eq;
+using ::testing::InSequence;
+using ::testing::StrEq;
+
+static constexpr Class kClass1{"Class1", Constructor{}, Constructor<jint>{},
+                               Method{"Foo", Return{Class{"Class2"}}}};
+
+static constexpr Class kClass2{
+    "Class2", Constructor{}, Constructor{kClass1},
+    Method{"Foo", jni::Return{}, jni::Params{kClass1}}};
+
+static constexpr Class kClass3{"Class3"};
+static constexpr Class kClass4{"Class4"};
+
+static constexpr ClassLoader kClassLoader{kNullClassLoader,
+                                          SupportedClassSet{kClass1, kClass2}};
+
+static constexpr Jvm kJvm{kClassLoader};
+
+// Helper class for classloader tests that gives the standard default class
+// object when using CallObjectMethodV.  This is because objects built from
+// class loaders are built by calling "loadClass" on the respective classloader
+// instance.  JniTest is strict about the number of DeleteGlobalRef calls,
+// so this satisfies that requirement.
+//
+// Note, when using this, you must call |TearDown| to pre-empt and class
+// teardown prior to expectations being set.
+class JniTestForClassLoaders : public JniTestWithNoDefaultJvmRef {
+  void SetUp() override {
+    JniTestWithNoDefaultJvmRef::SetUp();
+
+    ON_CALL(*env_, CallObjectMethodV)
+        .WillByDefault(::testing::Return(Fake<jclass>()));
+  }
+};
+
+TEST_F(JniTest, LocalsAreMoveable) {
+  LocalClassLoader<kClassLoader, kJvm> obj_1{Fake<jobject>()};
+  LocalClassLoader<kClassLoader, kJvm> obj_2{std::move(obj_1)};
+}
+
+TEST_F(JniTest, GlobalClassLoadersSupportAdoptionMechanics) {
+  EXPECT_CALL(*env_, DeleteLocalRef).Times(0);
+  GlobalClassLoader<kClassLoader, kJvm> obj_1{AdoptGlobal{}, Fake<jobject>()};
+}
+
+TEST_F(JniTest, GlobalClassLoadersSupportPromoteMechanics) {
+  EXPECT_CALL(*env_, DeleteLocalRef).Times(1);
+  GlobalClassLoader<kClassLoader, kJvm> obj_1{PromoteToGlobal{},
+                                              Fake<jobject>()};
+}
+
+TEST_F(JniTest, GlobalsAreMoveable) {
+  GlobalClassLoader<kClassLoader, kJvm> obj_1{AdoptGlobal{}, Fake<jobject>()};
+  GlobalClassLoader<kClassLoader, kJvm> obj_2{std::move(obj_1)};
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Default JVM, non-default classloader (No ID teardown on JVM destruction).
+//
+// Because these tests use |jni::kDefaultJvm|, the global refs bound to class
+// and method IDs won't be freed (they are static across the process).  As a
+// hack,  skip testing for them by calling clearing expected globals.
+//
+// An alternate (more effective) emulation would be to have these tests run
+// as independent processes which would reflect the static nature of the memory.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, LocalObject_SupportsPassingAnObjectWithAClassLoader) {
+  JvmRef<kDefaultJvm> jvm_ref{jvm_.get(), kDefaultConfiguration};
+
+  // LocalObject<kClass2, kClassLoader> a{}; // doesn't compile (good).
+  LocalObject<kClass1, kClassLoader> a{Fake<jobject>()};
+  LocalObject<kClass2> b{};
+  b("Foo", a);
+
+  default_globals_made_that_should_be_released_.clear();
+}
+
+TEST_F(JniTestForClassLoaders,
+       ClassLoaderRefTest_ConstructorsAcceptClassLoadedObjects) {
+  JvmRef<kDefaultJvm> jvm_ref{jvm_.get(), kDefaultConfiguration};
+
+  LocalClassLoader<kClassLoader> local_class_loader{Fake<jobject>()};
+  auto a = local_class_loader.BuildLocalObject<kClass1>();
+  LocalObject<kClass2> b{a};
+  b("Foo", a);
+
+  default_globals_made_that_should_be_released_.clear();
+  TearDown();
+}
+
+TEST_F(JniTestForClassLoaders,
+       lassLoaderRefTest_ConstructorsAcceptGlobalClassLoadedObjects) {
+  JvmRef<kDefaultJvm> jvm_ref{jvm_.get(), kDefaultConfiguration};
+
+  LocalClassLoader<kClassLoader> local_class_loader{Fake<jobject>()};
+  auto a = local_class_loader.BuildGlobalObject<kClass1>();
+  LocalObject<kClass2> b{a};
+  b("Foo", a);
+
+  default_globals_made_that_should_be_released_.clear();
+  TearDown();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Non standard JVM, non-default classloader (ID teardown on JVM destruction).
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTestForClassLoaders,
+       ClassLoaderRefTest_DefaultLoadedObjectBuildsWithClassLoadedObject) {
+  JvmRef<kJvm> jvm_ref{jvm_.get(), kDefaultConfiguration};
+  LocalClassLoader<kClassLoader, kJvm> local_class_loader{AdoptLocal{},
+                                                          Fake<jobject>()};
+  LocalObject<kClass1, kClassLoader> a =
+      local_class_loader.BuildLocalObject<kClass1>();
+  LocalObject<kClass2> b{a};
+  b("Foo", a);
+
+  TearDown();
+}
+
+TEST_F(JniTestForClassLoaders, ClassLoaderRefTest_ConstructsFromRValue) {
+  JvmRef<kJvm> jvm_ref{jvm_.get(), kDefaultConfiguration};
+
+  LocalClassLoader<kClassLoader, kJvm> local_class_loader{Fake<jobject>()};
+  LocalObject<kClass1, kClassLoader, kJvm> b{
+      local_class_loader.BuildLocalObject<kClass1>()};
+
+  LocalObject<kClass2, kClassLoader, kJvm> c{b("Foo")};
+
+  TearDown();
+}
+
+TEST_F(JniTestForClassLoaders,
+       ClassLoaderRefTest_ClassLoadedObjectBuildsWithDefaultLoadedObject) {
+  JvmRef<kJvm> jvm_ref{jvm_.get(), kDefaultConfiguration};
+
+  LocalClassLoader<kClassLoader, kJvm> local_class_loader{Fake<jobject>()};
+
+  LocalObject<kClass1> a{};
+  LocalObject<kClass2, kClassLoader, kJvm> b =
+      local_class_loader.BuildLocalObject<kClass2>(a);
+  b("Foo", a);
+
+  TearDown();
+}
+
+TEST_F(
+    JniTestForClassLoaders,
+    ClassLoaderRefTest_LocalClassLoaderWithSingleClassAndConstructorCompiles) {
+  JvmRef<kJvm> jvm_ref{jvm_.get(), kDefaultConfiguration};
+
+  LocalClassLoader<kClassLoader, kJvm> local_class_loader{Fake<jobject>()};
+  auto a = local_class_loader.BuildLocalObject<kClass1>(12345);
+  // local_class_loader.BuildLocalObject<kClass1>(); doesn't compile (good)
+
+  TearDown();
+}
+
+TEST_F(JniTestWithNoDefaultJvmRef,
+       ClassLoaderRefTest_LocalClassLoaderWithMultipleClassesCompiles) {
+  ON_CALL(*env_, CallObjectMethodV)
+      .WillByDefault(::testing::Return(Fake<jclass>()));
+
+  static constexpr ClassLoader kClassLoader{
+      kNullClassLoader, SupportedClassSet{kClass1, kClass2, kClass3, kClass4}};
+  static constexpr Jvm kJvm{kClassLoader};
+  JvmRef<kJvm> jvm_ref{jvm_.get(), kDefaultConfiguration};
+
+  LocalClassLoader<kClassLoader, kJvm> local_class_loader{Fake<jobject>()};
+
+  LocalObject a{local_class_loader.BuildLocalObject<kClass1>()};
+  auto b = local_class_loader.BuildLocalObject<kClass2>();
+  auto c = local_class_loader.BuildLocalObject<kClass3>();
+  auto d = local_class_loader.BuildLocalObject<kClass4>();
+
+  TearDown();
+}
+
+TEST_F(JniTestWithNoDefaultJvmRef,
+       DefaultLoadedObjectAcceptsClassLoadedObject) {
+  ON_CALL(*env_, CallObjectMethodV(testing::_, testing::_, testing::_))
+      .WillByDefault(::testing::Return(Fake<jclass>()));
+
+  JvmRef<kJvm> jvm_ref{jvm_.get(), kDefaultConfiguration};
+
+  LocalClassLoader<kClassLoader, kJvm> local_class_loader{Fake<jobject>()};
+
+  LocalObject<kClass1, kClassLoader, kJvm> a =
+      local_class_loader.BuildLocalObject<kClass1>();
+  LocalObject<kClass2> b{};
+  b("Foo", a);
+
+  TearDown();
+}
+
+TEST_F(JniTestWithNoDefaultJvmRef,
+       ClassLoaderRefTest_DefaultLoadedClassCompiles) {
+  ON_CALL(*env_, CallObjectMethodV(testing::_, testing::_, testing::_))
+      .WillByDefault(::testing::Return(Fake<jclass>()));
+  JvmRef<kJvm> jvm_ref{jvm_.get(), kDefaultConfiguration};
+
+  LocalClassLoader<kClassLoader, kJvm> local_class_loader{Fake<jobject>()};
+
+  LocalObject a{local_class_loader.BuildLocalObject<kClass1>()};
+  TearDown();
+}
+
+TEST_F(JniTestWithNoDefaultJvmRef,
+       ClassLoaderRefTest_ClassesOfDifferentClassLoadersAreUnique) {
+  static constexpr Class class_under_test{
+      "com/google/ARCore",
+      Method{"Foo", jni::Return{}},
+  };
+  static constexpr ClassLoader class_loader{
+      kNullClassLoader, SupportedClassSet{class_under_test}};
+
+  static constexpr jni::Jvm atypical_jvm_definition{class_loader};
+
+  InSequence seq;
+
+  // The java/lang/Class and java/lang/ClassLoader will always be from the
+  // default loader, and they only need to be cached once.
+  EXPECT_CALL(*env_, FindClass(StrEq("java/lang/Class")))
+      .WillOnce(testing::Return(Fake<jclass>(2)));
+
+  EXPECT_CALL(*env_, FindClass(StrEq("java/lang/ClassLoader")))
+      .WillOnce(testing::Return(Fake<jclass>(3)));
+
+  EXPECT_CALL(*env_, GetObjectClass(Fake<jobject>(1)))
+      .WillOnce(testing::Return(Fake<jclass>(1)));
+
+  EXPECT_CALL(*env_,
+              GetMethodID(AsGlobal(Fake<jclass>(2)), StrEq("getClassLoader"),
+                          StrEq("()Ljava/lang/ClassLoader;")))
+      .WillOnce(testing::Return(Fake<jmethodID>(2)));
+
+  EXPECT_CALL(*env_, CallObjectMethodV(Fake<jclass>(1), Fake<jmethodID>(2), _))
+      .WillOnce(testing::Return(Fake<jobject>(3)));
+
+  EXPECT_CALL(*env_,
+              GetMethodID(Eq(AsGlobal(Fake<jclass>(3))), StrEq("loadClass"),
+                          StrEq("(Ljava/lang/String;)Ljava/lang/Class;")))
+      .WillOnce(testing::Return(Fake<jmethodID>(1)));
+
+  // Note: While "/" is the mandatory delimiter for describing the class in its
+  // definition, load class uses "." delineation. Strangely, when calling
+  // Classloader.loadClass on Android both '.' and '/'work, but on x86 Java (and
+  // presumably other JVM implementations), only the "." is accepted.
+  EXPECT_CALL(*env_, NewStringUTF(StrEq("com.google.ARCore")))
+      .WillOnce(testing::Return(Fake<jstring>()));
+
+  EXPECT_CALL(*env_, CallObjectMethodV(Fake<jobject>(3), Fake<jmethodID>(1), _))
+      .WillOnce(testing::Return(Fake<jobject>(2)));
+
+  // Make sure we try to get the method with the loaded class, not the direct
+  // object class.
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("()V")))
+      .WillOnce(testing::Return(Fake<jmethodID>(1)));
+
+  // Code under test.
+  jni::JvmRef<atypical_jvm_definition> jvm_ref{jvm_.get(),
+                                               kDefaultConfiguration};
+  jni::LocalObject<class_under_test, class_loader, atypical_jvm_definition>
+      obj1{AdoptLocal{}, Fake<jobject>(1)};
+  obj1("Foo");
+
+  this->TearDown();
+}
+
+#endif  // __clang__
+
+}  // namespace
diff --git a/implementation/legacy/field_ref_test.cc b/implementation/legacy/field_ref_test.cc
new file mode 100644
index 00000000..70521495
--- /dev/null
+++ b/implementation/legacy/field_ref_test.cc
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <utility>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+namespace {
+
+#if __clang__
+
+using ::jni::AdoptLocal;
+using ::jni::Class;
+using ::jni::Fake;
+using ::jni::Field;
+using ::jni::GlobalObject;
+using ::jni::LocalObject;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::Return;
+using ::testing::StrEq;
+
+// clang-format off
+static constexpr Class java_class_under_test{
+    "com/google/TestClass",
+    Field{"booleanField", jboolean{}},
+    Field{"byteField", jbyte{}},
+    Field{"charField", jchar{}},
+    Field{"shortField", jshort{}},
+    Field{"intField", jint{}},
+    Field{"longField", jlong{}},
+    Field{"floatField", jfloat{}},
+    Field{"doubleField", jdouble{}}
+};
+// clang-format on
+
+TEST_F(JniTest, Field_SimpleGetAndSet) {
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("SomeField"), StrEq("I")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetIntField(_, Fake<jfieldID>())).WillOnce(Return(999));
+  EXPECT_CALL(*env_, SetIntField(_, Fake<jfieldID>(), 123));
+
+  static constexpr Class java_class_under_test{"com/google/TestClass",
+                                               Field("SomeField", jint{})};
+
+  GlobalObject<java_class_under_test> obj{};
+  EXPECT_EQ(999, obj["SomeField"].Get());
+  obj["SomeField"].Set(123);
+}
+
+TEST_F(JniTest, Field_BooleanField) {
+  EXPECT_CALL(*env_, GetBooleanField);
+  EXPECT_CALL(*env_, SetBooleanField);
+
+  LocalObject<java_class_under_test> obj{};
+  obj["booleanField"].Get();
+  obj["booleanField"].Set(true);
+}
+
+TEST_F(JniTest, Field_ByteField) {
+  EXPECT_CALL(*env_, GetByteField);
+  EXPECT_CALL(*env_, SetByteField);
+
+  LocalObject<java_class_under_test> obj{};
+  obj["byteField"].Get();
+  obj["byteField"].Set(jbyte{123});
+}
+
+TEST_F(JniTest, Field_CharField) {
+  EXPECT_CALL(*env_, GetCharField);
+  EXPECT_CALL(*env_, SetCharField);
+
+  LocalObject<java_class_under_test> obj{};
+  obj["charField"].Get();
+  obj["charField"].Set(jchar{'a'});
+}
+
+TEST_F(JniTest, Field_ShortField) {
+  EXPECT_CALL(*env_, GetShortField);
+  EXPECT_CALL(*env_, SetShortField);
+
+  LocalObject<java_class_under_test> obj{};
+  obj["shortField"].Get();
+  obj["shortField"].Set(jshort{123});
+}
+
+TEST_F(JniTest, Field_intField) {
+  EXPECT_CALL(*env_, GetIntField);
+  EXPECT_CALL(*env_, SetIntField);
+
+  LocalObject<java_class_under_test> obj{};
+  obj["intField"].Get();
+  obj["intField"].Set(123);
+}
+
+TEST_F(JniTest, Field_longField) {
+  EXPECT_CALL(*env_, GetLongField);
+  EXPECT_CALL(*env_, SetLongField);
+
+  LocalObject<java_class_under_test> obj{};
+  obj["longField"].Get();
+  obj["longField"].Set(123);
+}
+
+TEST_F(JniTest, Field_floatField) {
+  LocalObject<java_class_under_test> obj{};
+  EXPECT_CALL(*env_, GetFloatField);
+  EXPECT_CALL(*env_, SetFloatField);
+  obj["floatField"].Get();
+  obj["floatField"].Set(123.f);
+}
+
+TEST_F(JniTest, Field_doubleField) {
+  LocalObject<java_class_under_test> obj{};
+  EXPECT_CALL(*env_, GetDoubleField);
+  EXPECT_CALL(*env_, SetDoubleField);
+  obj["doubleField"].Get();
+  obj["doubleField"].Set(123.);
+}
+
+TEST_F(JniTest, Field_ObjectGet) {
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("classField"), StrEq("LkClass2;")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetObjectField(_, Fake<jfieldID>()))
+      .WillOnce(Return(Fake<jobject>()));
+
+  static constexpr Class kClass{"com/google/TestClass",
+                                Field{"classField", Class{"kClass2"}}};
+  static constexpr Class kClass2{"kClass2"};
+
+  LocalObject<kClass> obj{};
+  LocalObject<kClass2> obj2 = obj["classField"].Get();
+}
+
+TEST_F(JniTest, Field_ObjectSet) {
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("classField"), StrEq("LkClass2;")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, SetObjectField(_, Fake<jfieldID>(), Fake<jobject>()))
+      .Times(4);
+
+  static constexpr Class kClass2{"kClass2"};
+  static constexpr Class kClass{
+      "com/google/TestClass",
+      // Field{"classField", Class{"kClass2"}}}; // also works
+      Field{"classField", kClass2}};
+
+  LocalObject<kClass> obj{};
+  LocalObject<kClass2> some_obj{AdoptLocal{}, Fake<jobject>()};
+  obj["classField"].Set(Fake<jobject>());
+  obj["classField"].Set(LocalObject<kClass2>{AdoptLocal{}, Fake<jobject>()});
+  obj["classField"].Set(some_obj);
+  obj["classField"].Set(std::move(some_obj));
+}
+
+#endif  // __clang__
+
+}  // namespace
diff --git a/implementation/legacy/global_object_test.cc b/implementation/legacy/global_object_test.cc
new file mode 100644
index 00000000..60b42f54
--- /dev/null
+++ b/implementation/legacy/global_object_test.cc
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <tuple>
+#include <utility>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+namespace {
+
+#if __clang__
+
+using ::jni::AdoptGlobal;
+using ::jni::AdoptLocal;
+using ::jni::Class;
+using ::jni::Constructor;
+using ::jni::Fake;
+using ::jni::Field;
+using ::jni::GlobalObject;
+using ::jni::LocalObject;
+using ::jni::Method;
+using ::jni::NewRef;
+using ::jni::Params;
+using ::jni::PromoteToGlobal;
+using ::jni::test::AsGlobal;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::StrEq;
+
+TEST_F(JniTest, GlobalObject_AllowsNullPtrT) {
+  static constexpr Class kClass{"kClass"};
+  EXPECT_CALL(*env_, NewLocalRef).Times(0);
+  EXPECT_CALL(*env_, NewGlobalRef).Times(0);
+  EXPECT_CALL(*env_, DeleteLocalRef).Times(0);
+  EXPECT_CALL(*env_, DeleteGlobalRef).Times(0);
+
+  GlobalObject<kClass> obj{nullptr};
+  EXPECT_EQ(jobject{obj}, nullptr);
+}
+
+TEST_F(JniTest, GlobalObject_CallsNewAndDeleteOnNewObject) {
+  static constexpr Class kClass{"kClass"};
+
+  EXPECT_CALL(*env_, NewObjectV).WillOnce(Return(Fake<jobject>()));
+  EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake<jobject>())));
+
+  GlobalObject<kClass> global_object{};
+
+  EXPECT_EQ(jobject{global_object}, AsGlobal(Fake<jobject>()));
+}
+
+TEST_F(JniTest, GlobalObject_ConstructsFromNonStandardConstructor) {
+  static constexpr Class kClass{
+      "kClass",
+      Constructor{jfloat{}, jfloat{}},
+  };
+
+  EXPECT_CALL(*env_, NewObjectV).WillOnce(Return(Fake<jobject>()));
+  EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake<jobject>())));
+
+  GlobalObject<kClass> global_object{1.f, 2.f};
+
+  EXPECT_EQ(jobject{global_object}, AsGlobal(Fake<jobject>()));
+}
+
+TEST_F(JniTest, GlobalObject_ComparesAgainstOtherGlobalObjects) {
+  static constexpr Class kClass{
+      "kClass",
+      Constructor{jfloat{}, jfloat{}},
+  };
+  static constexpr Class kClass2{
+      "kClass2",
+  };
+  GlobalObject<kClass> val_1{AdoptGlobal{}, Fake<jobject>(1)};
+  GlobalObject<kClass2> val_2{AdoptGlobal{}, Fake<jobject>(2)};
+
+  EXPECT_TRUE(val_1 == val_1);
+  EXPECT_FALSE(val_1 == val_2);
+  EXPECT_TRUE(val_1 != val_2);
+  EXPECT_TRUE(val_2 == val_2);
+  EXPECT_TRUE(val_2 != val_1);
+  EXPECT_FALSE(val_1 == val_2);
+}
+
+TEST_F(JniTest, GlobalObject_DoesNotDeleteAnyLocalsForAdoptedGlobalJobject) {
+  static constexpr Class kClass{"kClass"};
+
+  EXPECT_CALL(*env_, NewObjectV).Times(0);
+  EXPECT_CALL(*env_, DeleteLocalRef).Times(0);
+  EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jobject>()));
+
+  GlobalObject<kClass> global_object{AdoptGlobal{}, Fake<jobject>()};
+
+  EXPECT_EQ(jobject{global_object}, Fake<jobject>());
+}
+
+TEST_F(JniTest, GlobalObject_PromotesJobjectsOnConstruction) {
+  EXPECT_CALL(*env_, NewObjectV).Times(0);
+  EXPECT_CALL(*env_, DeleteLocalRef).Times(1);
+  EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake<jobject>())));
+
+  static constexpr Class kClass{"kClass"};
+  GlobalObject<kClass> global_object{PromoteToGlobal{}, Fake<jobject>()};
+  EXPECT_EQ(jobject{global_object}, AsGlobal(Fake<jobject>()));
+}
+
+TEST_F(JniTest, GlobalObject_PromotesDecoratedLocals) {
+  EXPECT_CALL(*env_, NewObjectV).Times(0);
+  EXPECT_CALL(*env_, DeleteLocalRef).Times(1);
+  EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake<jobject>())));
+
+  static constexpr Class kClass{"kClass"};
+  LocalObject<kClass> local_obj{AdoptLocal{}, Fake<jobject>()};
+  // GlobalObject<kClass> global_object{local_obj}; // doesn't compile (good).
+  GlobalObject<kClass> global_object{std::move(local_obj)};
+
+  EXPECT_EQ(jobject{global_object}, AsGlobal(Fake<jobject>()));
+}
+
+// Identical to above but Local constructed in place.
+TEST_F(JniTest, GlobalObject_PromotesDecoratedLocalsFromXValue) {
+  EXPECT_CALL(*env_, NewObjectV).Times(0);
+  EXPECT_CALL(*env_, DeleteLocalRef).Times(1);
+  EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake<jobject>())));
+
+  static constexpr Class kClass{"kClass"};
+  GlobalObject<kClass> global_object{
+      LocalObject<kClass>{AdoptLocal{}, Fake<jobject>()}};
+
+  EXPECT_EQ(jobject{global_object}, AsGlobal(Fake<jobject>()));
+}
+
+TEST_F(JniTest, GlobalObject_CallsOnlyDeleteOnWrapCtor) {
+  EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jobject>()));
+
+  static constexpr Class kClass{"com/google/CallsOnlyDeleteOnWrapCtor"};
+  GlobalObject<kClass> global_object{AdoptGlobal{}, Fake<jobject>()};
+
+  EXPECT_NE(jobject{global_object}, nullptr);
+}
+
+TEST_F(JniTest, GlobalObject_CallsNewGlobalRefOnCopy) {
+  static constexpr Class kClass{"kClass"};
+
+  EXPECT_CALL(*env_, NewGlobalRef(Fake<jobject>(1)))
+      .WillOnce(::testing::Return(Fake<jobject>(2)));
+  EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jobject>(2)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(1))).Times(0);
+
+  GlobalObject<kClass> global_object{NewRef{}, Fake<jobject>(1)};
+  EXPECT_EQ(jobject{global_object}, Fake<jobject>(2));
+}
+
+TEST_F(JniTest, GlobalObject_CallsDeleteOnceAfterAMoveConstruction) {
+  EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jobject>()));
+
+  static constexpr Class kClass{
+      "com/google/CallsDeleteOnceAfterAMoveConstruction"};
+  GlobalObject<kClass> global_object_1{AdoptGlobal{}, Fake<jobject>()};
+  EXPECT_EQ(jobject{global_object_1}, Fake<jobject>());
+  GlobalObject<kClass> global_object_2{std::move(global_object_1)};
+
+  EXPECT_EQ(jobject{global_object_1}, nullptr);  // NOLINT
+  EXPECT_EQ(jobject{global_object_2}, Fake<jobject>());
+}
+
+TEST_F(JniTest, GlobalObject_FunctionsProperlyInSTLContainer) {
+  EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jobject>(1)));
+  EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jobject>(2)));
+
+  static constexpr Class kClass{
+      "com/google/CallsDeleteOnceAfterAMoveConstruction"};
+  GlobalObject<kClass> global_object_1{AdoptGlobal{}, Fake<jobject>(1)};
+  GlobalObject<kClass> global_object_2{AdoptGlobal{}, Fake<jobject>(2)};
+  std::tuple t{std::move(global_object_1), std::move(global_object_2)};
+}
+
+TEST_F(JniTest, GlobalObject_ValuesWorkAfterMoveConstructor) {
+  EXPECT_CALL(*env_, CallIntMethodV).Times(3);
+  EXPECT_CALL(*env_, SetIntField).Times(4);
+
+  static constexpr Class kClass{
+      "com/google/ValuesWorkAfterMoveConstructor",
+      Method{"Foo", jni::Return<jint>{}, Params<jint>{}},
+      Field{"BarField", jint{}}};
+  GlobalObject<kClass> global_object_1{AdoptGlobal{}, Fake<jobject>(1)};
+  global_object_1("Foo", 1);
+  global_object_1("Foo", 2);
+  global_object_1["BarField"].Set(1);
+
+  GlobalObject<kClass> global_object_2{std::move(global_object_1)};
+  global_object_2("Foo", 3);
+  global_object_2["BarField"].Set(2);
+  global_object_2["BarField"].Set(3);
+  global_object_2["BarField"].Set(4);
+
+  GlobalObject<kClass> global_object_3{AdoptGlobal{}, Fake<jobject>(1)};
+}
+
+TEST_F(JniTest, GlobalObject_ObjectReturnsInstanceMethods) {
+  InSequence seq;
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("<init>"), StrEq("()V")))
+      .WillOnce(Return(Fake<jmethodID>(1)));
+  EXPECT_CALL(*env_, NewObjectV(_, Fake<jmethodID>(1), _))
+      .WillOnce(Return(Fake<jobject>()));
+
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)I")))
+      .WillOnce(Return(Fake<jmethodID>(2)));
+  EXPECT_CALL(*env_, CallIntMethodV(_, _, _));
+
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("(F)V")))
+      .WillOnce(Return(Fake<jmethodID>(2)));
+  EXPECT_CALL(*env_, CallVoidMethodV(_, _, _));
+
+  EXPECT_CALL(*env_,
+              GetMethodID(_,
+                          StrEq("AMethodWithAReallyLongNameThatWouldPossiblyBeH"
+                                "ardForTemplatesToHandle"),
+                          StrEq("(IFIFD)D")));
+  EXPECT_CALL(*env_, CallDoubleMethodV(_, _, _));
+
+  static constexpr Class java_class_under_test{
+      "com/google/ObjectReturnsInstanceMethods",
+      Method{"Foo", jni::Return<jint>{}, Params<jint>{}},
+      Method{"Baz", jni::Return<void>{}, Params<jfloat>{}},
+      Method{"AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplates"
+             "ToHandle",
+             jni::Return<jdouble>{},
+             Params<jint, jfloat, jint, jfloat, jdouble>{}}};
+
+  GlobalObject<java_class_under_test> global_object{};
+  global_object("Foo", 1);
+  global_object("Baz", 2.f);
+  global_object(
+      "AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplatesToHandle",
+      int{}, float{}, int{}, float{}, double{});
+}
+
+TEST_F(JniTest, GlobalObject_ReleasesGlobalsForAlternateConstructors) {
+  static constexpr Class java_class_under_test{
+      "ReleasesGlobalsForAlternateConstructors", jni::Constructor<int>{}};
+  GlobalObject<java_class_under_test> g1{1};
+  GlobalObject<java_class_under_test> g2{2};
+  GlobalObject<java_class_under_test> g3{3};
+  EXPECT_CALL(*env_, DeleteGlobalRef(_)).Times(3);
+}
+
+TEST_F(JniTest, GlobalObject_SupportsPassingAPrvalue) {
+  static constexpr Class kTestClass1{"TestClass1"};
+  static constexpr Class kTestClass2{
+      "TestClass2", Method{"Foo", jni::Return{}, jni::Params{kTestClass1}}};
+
+  GlobalObject<kTestClass1> a{};
+  GlobalObject<kTestClass2> b{};
+  b("Foo", std::move(a));
+}
+
+TEST_F(JniTest, GlobalObjects_PromoteRValuesFromEmittedLValues) {
+  static constexpr Class kClass1{"TestClass1"};
+  static constexpr Class kClass2{
+      "TestClass2", Method{"Foo", jni::Return{kClass1}, jni::Params{}}};
+
+  LocalObject<kClass2> b{};
+  GlobalObject<kClass1> a{b("Foo")};
+
+  a = b("Foo");
+}
+
+#endif  // __clang__
+
+}  // namespace
diff --git a/implementation/legacy/local_array_field_multidimensional_test.cc b/implementation/legacy/local_array_field_multidimensional_test.cc
new file mode 100644
index 00000000..cba7e3a8
--- /dev/null
+++ b/implementation/legacy/local_array_field_multidimensional_test.cc
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+namespace {
+
+#if __clang__
+
+using ::jni::AdoptLocal;
+using ::jni::Array;
+using ::jni::Class;
+using ::jni::Fake;
+using ::jni::Field;
+using ::jni::LocalObject;
+using ::jni::Rank;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::StrEq;
+
+static constexpr Class kClass{"kClass"};
+
+static constexpr Class kRank2{
+    "kFieldClass",
+    Field{"BooleanArray", Array{jboolean{}, Rank<2>{}}},
+    Field{"ByteArray", Array{jbyte{}, Rank<2>{}}},
+    Field{"CharArray", Array{jchar{}, Rank<2>{}}},
+    Field{"ShortArray", Array{jshort{}, Rank<2>{}}},
+    Field{"IntArray", Array{jint{}, Rank<2>{}}},
+    Field{"FloatArray", Array{jfloat{}, Rank<2>{}}},
+    Field{"DoubleArray", Array{jdouble{}, Rank<2>{}}},
+    Field{"LongArray", Array{jlong{}, Rank<2>{}}},
+    Field{"ObjectArray", Array{kClass, Rank<2>{}}},
+};
+
+TEST_F(JniTest, LocalArrayField_Rank2) {
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("BooleanArray"), StrEq("[[Z")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("ByteArray"), StrEq("[[B")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("CharArray"), StrEq("[[C")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("ShortArray"), StrEq("[[S")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("IntArray"), StrEq("[[I")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("FloatArray"), StrEq("[[F")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("DoubleArray"), StrEq("[[D")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("LongArray"), StrEq("[[J")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("ObjectArray"), StrEq("[[LkClass;")));
+
+  EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>()))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(1)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(2)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(3)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(4)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(5)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(6)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(7)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(8)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(9)));
+
+  LocalObject<kRank2> obj{AdoptLocal{}, Fake<jobject>()};
+
+  EXPECT_EQ(static_cast<jobjectArray>(obj["BooleanArray"].Get()),
+            Fake<jobjectArray>(1));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["ByteArray"].Get()),
+            Fake<jobjectArray>(2));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["CharArray"].Get()),
+            Fake<jobjectArray>(3));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["ShortArray"].Get()),
+            Fake<jobjectArray>(4));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["IntArray"].Get()),
+            Fake<jobjectArray>(5));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["FloatArray"].Get()),
+            Fake<jobjectArray>(6));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["DoubleArray"].Get()),
+            Fake<jobjectArray>(7));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["LongArray"].Get()),
+            Fake<jobjectArray>(8));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["ObjectArray"].Get()),
+            Fake<jobjectArray>(9));
+}
+
+static constexpr Class kRank3{
+    "kFieldClass",
+    Field{"BooleanArray", Array{jboolean{}, Rank<3>{}}},
+    Field{"ByteArray", Array{jbyte{}, Rank<3>{}}},
+    Field{"CharArray", Array{jchar{}, Rank<3>{}}},
+    Field{"ShortArray", Array{jshort{}, Rank<3>{}}},
+    Field{"IntArray", Array{jint{}, Rank<3>{}}},
+    Field{"FloatArray", Array{jfloat{}, Rank<3>{}}},
+    Field{"DoubleArray", Array{jdouble{}, Rank<3>{}}},
+    Field{"LongArray", Array{jlong{}, Rank<3>{}}},
+    Field{"ObjectArray", Array{kClass, Rank<3>{}}},
+};
+
+TEST_F(JniTest, LocalArrayField_Rank3) {
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("BooleanArray"), StrEq("[[[Z")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("ByteArray"), StrEq("[[[B")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("CharArray"), StrEq("[[[C")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("ShortArray"), StrEq("[[[S")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("IntArray"), StrEq("[[[I")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("FloatArray"), StrEq("[[[F")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("DoubleArray"), StrEq("[[[D")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("LongArray"), StrEq("[[[J")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("ObjectArray"), StrEq("[[[LkClass;")));
+
+  EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>()))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(1)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(2)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(3)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(4)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(5)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(6)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(7)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(8)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(9)));
+
+  LocalObject<kRank3> obj{AdoptLocal{}, Fake<jobject>()};
+
+  EXPECT_EQ(static_cast<jobjectArray>(obj["BooleanArray"].Get()),
+            Fake<jobjectArray>(1));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["ByteArray"].Get()),
+            Fake<jobjectArray>(2));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["CharArray"].Get()),
+            Fake<jobjectArray>(3));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["ShortArray"].Get()),
+            Fake<jobjectArray>(4));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["IntArray"].Get()),
+            Fake<jobjectArray>(5));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["FloatArray"].Get()),
+            Fake<jobjectArray>(6));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["DoubleArray"].Get()),
+            Fake<jobjectArray>(7));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["LongArray"].Get()),
+            Fake<jobjectArray>(8));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["ObjectArray"].Get()),
+            Fake<jobjectArray>(9));
+}
+
+#endif  // __clang__
+
+}  // namespace
diff --git a/implementation/legacy/local_array_field_test.cc b/implementation/legacy/local_array_field_test.cc
new file mode 100644
index 00000000..7bcfc97a
--- /dev/null
+++ b/implementation/legacy/local_array_field_test.cc
@@ -0,0 +1,484 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+namespace {
+
+#if __clang__
+
+using ::jni::AdoptLocal;
+using ::jni::Array;
+using ::jni::Class;
+using ::jni::Fake;
+using ::jni::Field;
+using ::jni::LocalArray;
+using ::jni::LocalObject;
+using ::jni::Rank;
+using ::jni::test::AsNewLocalReference;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::StrEq;
+
+static constexpr Class kClass2{"kClass2"};
+
+////////////////////////////////////////////////////////////////////////////////
+// Fields: Smoke Tests.
+////////////////////////////////////////////////////////////////////////////////
+static constexpr Class kFieldClass{
+    "kFieldClass",
+    Field{"BooleanArray", Array{jboolean{}}},
+    Field{"ByteArray", Array{jbyte{}}},
+    Field{"CharArray", Array{jchar{}}},
+    Field{"ShortArray", Array{jshort{}}},
+    Field{"IntArray", Array{jint{}}},
+    Field{"FloatArray", Array{jfloat{}}},
+    Field{"DoubleArray", Array{jdouble{}}},
+    Field{"LongArray", Array{jlong{}}},
+    Field{"ObjectArrayRank1", Array{kClass2}},
+    Field{"ObjectArrayRank2", Array{kClass2, Rank<2>{}}},
+    Field{"ObjectArrayRank3", Array{kClass2, Rank<3>{}}},
+};
+
+TEST_F(JniTest, ArrayField_Object_Iteration_Rank_1) {
+  EXPECT_CALL(*env_, GetObjectField)
+      .WillOnce(::testing::Return(Fake<jobjectArray>()));
+  EXPECT_CALL(*env_, GetArrayLength).WillOnce(::testing::Return(3));
+  EXPECT_CALL(*env_, GetObjectArrayElement)
+      .WillOnce(::testing::Return(Fake<jobject>(100)))
+      .WillOnce(::testing::Return(Fake<jobject>(101)))
+      .WillOnce(::testing::Return(Fake<jobject>(102)));
+
+  int i = 100;
+  LocalObject<kFieldClass> obj{Fake<jobject>()};
+  for (LocalObject<kClass2> a : obj["ObjectArrayRank1"].Get().Pin()) {
+    EXPECT_EQ(static_cast<jobject>(a), Fake<jobject>(i));
+    i++;
+  }
+}
+
+TEST_F(JniTest, ArrayField_Object_Iteration_Rank_2) {
+  EXPECT_CALL(*env_, GetObjectField)
+      .WillOnce(::testing::Return(Fake<jobjectArray>()));
+  EXPECT_CALL(*env_, GetArrayLength).WillOnce(::testing::Return(3));
+  EXPECT_CALL(*env_, GetObjectArrayElement)
+      .WillOnce(::testing::Return(Fake<jobject>(100)))
+      .WillOnce(::testing::Return(Fake<jobject>(101)))
+      .WillOnce(::testing::Return(Fake<jobject>(102)));
+
+  int i = 100;
+  LocalObject<kFieldClass> obj{Fake<jobject>(1)};
+  for (LocalArray<jobject, 1, kClass2> a :
+       obj["ObjectArrayRank2"].Get().Pin()) {
+    EXPECT_EQ(static_cast<jobject>(a), Fake<jobject>(i));
+    i++;
+  }
+}
+
+TEST_F(JniTest, Array_FieldTests) {
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("BooleanArray"), StrEq("[Z")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("ByteArray"), StrEq("[B")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("CharArray"), StrEq("[C")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("ShortArray"), StrEq("[S")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("IntArray"), StrEq("[I")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("FloatArray"), StrEq("[F")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("DoubleArray"), StrEq("[D")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("LongArray"), StrEq("[J")));
+  EXPECT_CALL(*env_,
+              GetFieldID(_, StrEq("ObjectArrayRank1"), StrEq("[LkClass2;")));
+  EXPECT_CALL(*env_,
+              GetFieldID(_, StrEq("ObjectArrayRank2"), StrEq("[[LkClass2;")));
+  EXPECT_CALL(*env_,
+              GetFieldID(_, StrEq("ObjectArrayRank3"), StrEq("[[[LkClass2;")));
+
+  EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>()))
+      .WillOnce(::testing::Return(Fake<jbooleanArray>()))
+      .WillOnce(::testing::Return(Fake<jbyteArray>()))
+      .WillOnce(::testing::Return(Fake<jcharArray>()))
+      .WillOnce(::testing::Return(Fake<jshortArray>()))
+      .WillOnce(::testing::Return(Fake<jintArray>()))
+      .WillOnce(::testing::Return(Fake<jfloatArray>()))
+      .WillOnce(::testing::Return(Fake<jdoubleArray>()))
+      .WillOnce(::testing::Return(Fake<jlongArray>()))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(1)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(2)))
+      .WillOnce(::testing::Return(Fake<jobjectArray>(3)));
+
+  LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()};
+
+  EXPECT_EQ(static_cast<jbooleanArray>(obj["BooleanArray"].Get()),
+            Fake<jbooleanArray>());
+  EXPECT_EQ(static_cast<jbyteArray>(obj["ByteArray"].Get()),
+            Fake<jbyteArray>());
+  EXPECT_EQ(static_cast<jcharArray>(obj["CharArray"].Get()),
+            Fake<jcharArray>());
+  EXPECT_EQ(static_cast<jshortArray>(obj["ShortArray"].Get()),
+            Fake<jshortArray>());
+  EXPECT_EQ(static_cast<jintArray>(obj["IntArray"].Get()), Fake<jintArray>());
+  EXPECT_EQ(static_cast<jfloatArray>(obj["FloatArray"].Get()),
+            Fake<jfloatArray>());
+  EXPECT_EQ(static_cast<jdoubleArray>(obj["DoubleArray"].Get()),
+            Fake<jdoubleArray>());
+  EXPECT_EQ(static_cast<jlongArray>(obj["LongArray"].Get()),
+            Fake<jlongArray>());
+  EXPECT_EQ(static_cast<jobjectArray>(obj["ObjectArrayRank1"].Get()),
+            Fake<jobjectArray>(1));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["ObjectArrayRank2"].Get()),
+            Fake<jobjectArray>(2));
+  EXPECT_EQ(static_cast<jobjectArray>(obj["ObjectArrayRank3"].Get()),
+            Fake<jobjectArray>(3));
+}
+
+TEST_F(JniTest, Array_Field_Boolean_Test) {
+  std::unique_ptr<jboolean> fake_storage_ptr(new jboolean());
+
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("BooleanArray"), StrEq("[Z")))
+      .WillRepeatedly(::testing::Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>()))
+      .WillRepeatedly(::testing::Return(Fake<jbooleanArray>()));
+  EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(),
+                                    Fake<jbooleanArray>()))
+      .Times(4);
+  EXPECT_CALL(*env_, GetBooleanArrayElements)
+      .WillRepeatedly(::testing::Return(fake_storage_ptr.get()));
+  EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Fake<jbooleanArray>(),
+                                                 fake_storage_ptr.get(), 0));
+  EXPECT_CALL(*env_,
+              ReleaseBooleanArrayElements(Fake<jbooleanArray>(),
+                                          fake_storage_ptr.get(), JNI_ABORT));
+
+  LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()};
+  LocalArray<jboolean> arr{obj["BooleanArray"].Get()};
+  LocalArray<jboolean> arr2{AdoptLocal{}, Fake<jbooleanArray>()};
+  obj["BooleanArray"].Set(Fake<jbooleanArray>());
+  obj["BooleanArray"].Set(
+      LocalArray<jboolean>{AdoptLocal{}, Fake<jbooleanArray>()});
+  obj["BooleanArray"].Set(arr2);
+  obj["BooleanArray"].Set(obj["BooleanArray"].Get());
+
+  EXPECT_EQ(obj["BooleanArray"].Get().Pin().ptr(), fake_storage_ptr.get());
+  EXPECT_EQ(obj["BooleanArray"].Get().Pin(false).ptr(), fake_storage_ptr.get());
+}
+
+TEST_F(JniTest, Array_Field_Byte_Test) {
+  std::unique_ptr<jbyte> fake_storage_ptr(new jbyte());
+
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("ByteArray"), StrEq("[B")))
+      .WillRepeatedly(::testing::Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>()))
+      .WillRepeatedly(::testing::Return(Fake<jbyteArray>()));
+  EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(),
+                                    Fake<jbyteArray>()))
+      .Times(4);
+  EXPECT_CALL(*env_, GetByteArrayElements)
+      .WillRepeatedly(::testing::Return(fake_storage_ptr.get()));
+  EXPECT_CALL(*env_, ReleaseByteArrayElements(Fake<jbyteArray>(),
+                                              fake_storage_ptr.get(), 0));
+  EXPECT_CALL(*env_,
+              ReleaseByteArrayElements(Fake<jbyteArray>(),
+                                       fake_storage_ptr.get(), JNI_ABORT));
+
+  LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()};
+  LocalArray<jbyte> arr{obj["ByteArray"].Get()};
+  LocalArray<jbyte> arr2{AdoptLocal{}, Fake<jbyteArray>()};
+  obj["ByteArray"].Set(Fake<jbyteArray>());
+  obj["ByteArray"].Set(LocalArray<jbyte>{AdoptLocal{}, Fake<jbyteArray>()});
+  obj["ByteArray"].Set(arr2);
+  obj["ByteArray"].Set(obj["ByteArray"].Get());
+  EXPECT_EQ(obj["ByteArray"].Get().Pin().ptr(), fake_storage_ptr.get());
+  EXPECT_EQ(obj["ByteArray"].Get().Pin(false).ptr(), fake_storage_ptr.get());
+}
+
+TEST_F(JniTest, Array_Field_Char_Test) {
+  std::unique_ptr<jchar> fake_storage_ptr(new jchar());
+
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("CharArray"), StrEq("[C")))
+      .WillRepeatedly(::testing::Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>()))
+      .WillRepeatedly(::testing::Return(Fake<jcharArray>()));
+  EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(),
+                                    Fake<jcharArray>()))
+      .Times(4);
+  EXPECT_CALL(*env_, GetCharArrayElements)
+      .WillRepeatedly(::testing::Return(fake_storage_ptr.get()));
+  EXPECT_CALL(*env_, ReleaseCharArrayElements(Fake<jcharArray>(),
+                                              fake_storage_ptr.get(), 0));
+  EXPECT_CALL(*env_,
+              ReleaseCharArrayElements(Fake<jcharArray>(),
+                                       fake_storage_ptr.get(), JNI_ABORT));
+
+  LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()};
+  LocalArray<jchar> arr{obj["CharArray"].Get()};
+  LocalArray<jchar> arr2{AdoptLocal{}, Fake<jcharArray>()};
+  obj["CharArray"].Set(Fake<jcharArray>());
+  obj["CharArray"].Set(LocalArray<jchar>{AdoptLocal{}, Fake<jcharArray>()});
+  obj["CharArray"].Set(arr2);
+  obj["CharArray"].Set(obj["CharArray"].Get());
+  EXPECT_EQ(obj["CharArray"].Get().Pin().ptr(), fake_storage_ptr.get());
+  EXPECT_EQ(obj["CharArray"].Get().Pin(false).ptr(), fake_storage_ptr.get());
+}
+
+TEST_F(JniTest, Array_Field_Short_Test) {
+  std::unique_ptr<jshort> fake_storage_ptr(new jshort());
+
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("ShortArray"), StrEq("[S")))
+      .WillRepeatedly(::testing::Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>()))
+      .WillRepeatedly(::testing::Return(Fake<jshortArray>()));
+  EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(),
+                                    Fake<jshortArray>()))
+      .Times(4);
+  EXPECT_CALL(*env_, GetShortArrayElements)
+      .WillRepeatedly(::testing::Return(fake_storage_ptr.get()));
+  EXPECT_CALL(*env_, ReleaseShortArrayElements(Fake<jshortArray>(),
+                                               fake_storage_ptr.get(), 0));
+  EXPECT_CALL(*env_,
+              ReleaseShortArrayElements(Fake<jshortArray>(),
+                                        fake_storage_ptr.get(), JNI_ABORT));
+
+  LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()};
+  LocalArray<jshort> arr{obj["ShortArray"].Get()};
+  LocalArray<jshort> arr2{AdoptLocal{}, Fake<jshortArray>()};
+  obj["ShortArray"].Set(Fake<jshortArray>());
+  obj["ShortArray"].Set(LocalArray<jshort>{AdoptLocal{}, Fake<jshortArray>()});
+  obj["ShortArray"].Set(arr2);
+  obj["ShortArray"].Set(obj["ShortArray"].Get());
+  EXPECT_EQ(obj["ShortArray"].Get().Pin().ptr(), fake_storage_ptr.get());
+  EXPECT_EQ(obj["ShortArray"].Get().Pin(false).ptr(), fake_storage_ptr.get());
+}
+
+TEST_F(JniTest, Array_Field_Int_Test) {
+  std::unique_ptr<jint> fake_storage_ptr(new jint());
+
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("IntArray"), StrEq("[I")))
+      .WillRepeatedly(::testing::Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>()))
+      .WillRepeatedly(::testing::Return(Fake<jintArray>()));
+  EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(),
+                                    Fake<jintArray>()))
+      .Times(4);
+  EXPECT_CALL(*env_, GetIntArrayElements)
+      .WillRepeatedly(::testing::Return(fake_storage_ptr.get()));
+  EXPECT_CALL(*env_, ReleaseIntArrayElements(Fake<jintArray>(),
+                                             fake_storage_ptr.get(), 0));
+  EXPECT_CALL(*env_, ReleaseIntArrayElements(
+                         Fake<jintArray>(), fake_storage_ptr.get(), JNI_ABORT));
+
+  LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()};
+  LocalArray<jint> arr{obj["IntArray"].Get()};
+  LocalArray<jint> arr2{AdoptLocal{}, Fake<jintArray>()};
+  obj["IntArray"].Set(Fake<jintArray>());
+  obj["IntArray"].Set(LocalArray<jint>{AdoptLocal{}, Fake<jintArray>()});
+  obj["IntArray"].Set(arr2);
+  obj["IntArray"].Set(obj["IntArray"].Get());
+  EXPECT_EQ(obj["IntArray"].Get().Pin().ptr(), fake_storage_ptr.get());
+  EXPECT_EQ(obj["IntArray"].Get().Pin(false).ptr(), fake_storage_ptr.get());
+}
+
+TEST_F(JniTest, Array_Field_Float_Test) {
+  std::unique_ptr<jfloat> fake_storage_ptr(new jfloat());
+
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("FloatArray"), StrEq("[F")))
+      .WillRepeatedly(::testing::Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>()))
+      .WillRepeatedly(::testing::Return(Fake<jfloatArray>()));
+  EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(),
+                                    Fake<jfloatArray>()))
+      .Times(4);
+  EXPECT_CALL(*env_, GetFloatArrayElements)
+      .WillRepeatedly(::testing::Return(fake_storage_ptr.get()));
+  EXPECT_CALL(*env_, ReleaseFloatArrayElements(Fake<jfloatArray>(),
+                                               fake_storage_ptr.get(), 0));
+  EXPECT_CALL(*env_,
+              ReleaseFloatArrayElements(Fake<jfloatArray>(),
+                                        fake_storage_ptr.get(), JNI_ABORT));
+
+  LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()};
+  LocalArray<jfloat> arr{obj["FloatArray"].Get()};
+  LocalArray<jfloat> arr2{AdoptLocal{}, Fake<jfloatArray>()};
+  obj["FloatArray"].Set(Fake<jfloatArray>());
+  obj["FloatArray"].Set(LocalArray<jfloat>{AdoptLocal{}, Fake<jfloatArray>()});
+  obj["FloatArray"].Set(arr2);
+  obj["FloatArray"].Set(obj["FloatArray"].Get());
+  EXPECT_EQ(obj["FloatArray"].Get().Pin().ptr(), fake_storage_ptr.get());
+  EXPECT_EQ(obj["FloatArray"].Get().Pin(false).ptr(), fake_storage_ptr.get());
+}
+
+TEST_F(JniTest, Array_Field_Double_Test) {
+  std::unique_ptr<jdouble> fake_storage_ptr(new jdouble());
+
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("DoubleArray"), StrEq("[D")))
+      .WillRepeatedly(::testing::Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>()))
+      .WillRepeatedly(::testing::Return(Fake<jdoubleArray>()));
+  EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(),
+                                    Fake<jdoubleArray>()))
+      .Times(4);
+  EXPECT_CALL(*env_, GetDoubleArrayElements)
+      .WillRepeatedly(::testing::Return(fake_storage_ptr.get()));
+  EXPECT_CALL(*env_, ReleaseDoubleArrayElements(Fake<jdoubleArray>(),
+                                                fake_storage_ptr.get(), 0));
+  EXPECT_CALL(*env_,
+              ReleaseDoubleArrayElements(Fake<jdoubleArray>(),
+                                         fake_storage_ptr.get(), JNI_ABORT));
+
+  LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()};
+  LocalArray<jdouble> arr{obj["DoubleArray"].Get()};
+  LocalArray<jdouble> arr2{AdoptLocal{}, Fake<jdoubleArray>()};
+  obj["DoubleArray"].Set(Fake<jdoubleArray>());
+  obj["DoubleArray"].Set(
+      LocalArray<jdouble>{AdoptLocal{}, Fake<jdoubleArray>()});
+  obj["DoubleArray"].Set(arr2);
+  obj["DoubleArray"].Set(obj["DoubleArray"].Get());
+  EXPECT_EQ(obj["DoubleArray"].Get().Pin().ptr(), fake_storage_ptr.get());
+  EXPECT_EQ(obj["DoubleArray"].Get().Pin(false).ptr(), fake_storage_ptr.get());
+}
+
+TEST_F(JniTest, Array_Field_Long_Test) {
+  std::unique_ptr<jlong> fake_storage_ptr(new jlong());
+
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("LongArray"), StrEq("[J")))
+      .WillRepeatedly(::testing::Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>()))
+      .WillRepeatedly(::testing::Return(Fake<jlongArray>()));
+  EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(),
+                                    Fake<jlongArray>()))
+      .Times(4);
+  EXPECT_CALL(*env_, GetLongArrayElements)
+      .WillRepeatedly(::testing::Return(fake_storage_ptr.get()));
+  EXPECT_CALL(*env_, ReleaseLongArrayElements(Fake<jlongArray>(),
+                                              fake_storage_ptr.get(), 0));
+  EXPECT_CALL(*env_,
+              ReleaseLongArrayElements(Fake<jlongArray>(),
+                                       fake_storage_ptr.get(), JNI_ABORT));
+
+  LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()};
+  LocalArray<jlong> arr{obj["LongArray"].Get()};
+  LocalArray<jlong> arr2{AdoptLocal{}, Fake<jlongArray>()};
+  obj["LongArray"].Set(Fake<jlongArray>());
+  obj["LongArray"].Set(LocalArray<jlong>{AdoptLocal{}, Fake<jlongArray>()});
+  obj["LongArray"].Set(arr2);
+  obj["LongArray"].Set(obj["LongArray"].Get());
+  EXPECT_EQ(obj["LongArray"].Get().Pin().ptr(), fake_storage_ptr.get());
+  EXPECT_EQ(obj["LongArray"].Get().Pin(false).ptr(), fake_storage_ptr.get());
+}
+
+TEST_F(JniTest, Array_Field_Object_Test) {
+  EXPECT_CALL(*env_,
+              GetFieldID(_, StrEq("ObjectArrayRank1"), StrEq("[LkClass2;")))
+      .WillOnce(::testing::Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetObjectField(Fake<jobject>(), Fake<jfieldID>()))
+      .WillRepeatedly(::testing::Return(Fake<jobjectArray>()));
+  EXPECT_CALL(*env_, SetObjectField(Fake<jobject>(), Fake<jfieldID>(),
+                                    Fake<jobjectArray>()))
+      .Times(4);
+  EXPECT_CALL(*env_, GetObjectArrayElement(Fake<jobjectArray>(), 2));
+
+  LocalObject<kFieldClass> obj{AdoptLocal{}, Fake<jobject>()};
+  LocalArray<jobject, 1, kClass2> arr2{AdoptLocal{}, Fake<jobjectArray>()};
+  LocalArray<jobject, 1, kClass2> arr{obj["ObjectArrayRank1"].Get()};
+  obj["ObjectArrayRank1"].Set(Fake<jobjectArray>());
+  obj["ObjectArrayRank1"].Set(
+      LocalArray<jobject, 1, kClass2>{AdoptLocal{}, Fake<jobjectArray>()});
+  obj["ObjectArrayRank1"].Set(arr2);
+  obj["ObjectArrayRank1"].Set(obj["ObjectArrayRank1"].Get());
+  obj["ObjectArrayRank1"].Get().Get(2);
+}
+
+TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_1) {
+  static constexpr Class kClass2{"kClass2"};
+
+  static constexpr Class kClass{
+      "ArrayMultiTest",
+      Field{"Foo", Array{kClass2}},
+  };
+
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("Foo"), StrEq("[LkClass2;")));
+
+  LocalObject<kClass> obj{Fake<jobject>()};
+  LocalArray<jobject, 1, kClass2>{obj["Foo"].Get()};
+}
+
+TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_2) {
+  static constexpr Class kClass2{"kClass2"};
+
+  static constexpr Class kClass{
+      "kClass",
+      Field{"Foo", Array{kClass2, Rank<2>{}}},
+  };
+
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("Foo"), StrEq("[[LkClass2;")));
+
+  LocalObject<kClass> obj{Fake<jobject>()};
+  LocalArray<jobject, 2, kClass2> arr_from_field{obj["Foo"].Get()};
+}
+
+TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_2_Iteration) {
+  static constexpr Class kClass2{"kClass2"};
+
+  static constexpr Class kClass{
+      "kClass",
+      Field{"Foo", Array{kClass2, Rank<2>{}}},
+  };
+
+  InSequence seq;
+
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>()));
+
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("Foo"), StrEq("[[LkClass2;")));
+  EXPECT_CALL(*env_, GetObjectField)
+      .WillRepeatedly(::testing::Return(Fake<jobjectArray>()));
+  EXPECT_CALL(*env_, GetArrayLength).WillOnce(::testing::Return(3));
+
+  // A temporary local array is materialised in the field access.
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>()));
+
+  // Internal elements looped over.
+  EXPECT_CALL(*env_, GetObjectArrayElement)
+      .WillOnce(::testing::Return(Fake<jobject>(100)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(100)));
+
+  EXPECT_CALL(*env_, GetObjectArrayElement)
+      .WillOnce(::testing::Return(Fake<jobject>(101)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(101)));
+
+  EXPECT_CALL(*env_, GetObjectArrayElement)
+      .WillOnce(::testing::Return(Fake<jobject>(102)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(102)));
+
+  EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobjectArray>())));
+
+  // Object with queried array field.
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(1)));
+
+  LocalObject<kClass> obj{AdoptLocal{}, Fake<jobject>(1)};
+  int i = 100;
+  for (LocalArray<jobject> obj : obj["Foo"].Get().Pin()) {
+    EXPECT_EQ(static_cast<jobject>(obj), Fake<jobject>(i));
+    i++;
+  }
+}
+
+#endif  // __clang__
+
+}  // namespace
diff --git a/implementation/legacy/local_array_iteration_test.cc b/implementation/legacy/local_array_iteration_test.cc
new file mode 100644
index 00000000..81637ced
--- /dev/null
+++ b/implementation/legacy/local_array_iteration_test.cc
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <array>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+namespace {
+
+#if __clang__
+
+using ::jni::AdoptLocal;
+using ::jni::ArrayView;
+using ::jni::Class;
+using ::jni::Fake;
+using ::jni::LocalArray;
+using ::jni::LocalObject;
+using ::jni::test::AsNewLocalReference;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::Return;
+using ::testing::StrEq;
+
+static constexpr Class kClass{"kClass"};
+
+////////////////////////////////////////////////////////////////////////////////
+// Rank 0.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, IteratesOver1DRange) {
+  std::array expected{10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
+
+  EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(10));
+  EXPECT_CALL(*env_, GetIntArrayElements)
+      .WillRepeatedly(Return(expected.data()));
+
+  LocalArray<jint, 1> new_array{AdoptLocal{}, Fake<jintArray>()};
+
+  int i{0};
+  for (jint val : new_array.Pin()) {
+    EXPECT_EQ(val, expected[i]);
+    ++i;
+  }
+  EXPECT_EQ(i, 10);
+}
+
+TEST_F(JniTest, WorksWithSTLComparison) {
+  std::array expected{10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
+
+  EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(10));
+  EXPECT_CALL(*env_, GetIntArrayElements)
+      .WillRepeatedly(Return(expected.data()));
+
+  LocalArray<jint, 1> new_array{AdoptLocal{}, Fake<jintArray>()};
+  ArrayView array_view = new_array.Pin();
+  EXPECT_TRUE(
+      std::equal(array_view.begin(), array_view.end(), expected.begin()));
+}
+
+TEST_F(JniTest, WorksWithSTLComparisonOfObjects) {
+  std::array expected{Fake<jobject>(1), Fake<jobject>(2), Fake<jobject>(3)};
+
+  EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(3));
+  EXPECT_CALL(*env_, GetObjectArrayElement)
+      .WillOnce(Return(Fake<jobject>(1)))
+      .WillOnce(Return(Fake<jobject>(2)))
+      .WillOnce(Return(Fake<jobject>(3)));
+
+  LocalArray<jobject, 1, kClass> new_array{Fake<jobjectArray>()};
+  ArrayView array_view = new_array.Pin();
+  EXPECT_TRUE(
+      std::equal(array_view.begin(), array_view.end(), expected.begin()));
+}
+
+TEST_F(JniTest, WorksWithSTLComparisonOfRichlyDecoratedObjects) {
+  std::array expected{LocalObject<kClass>{AdoptLocal{}, Fake<jobject>(1)},
+                      LocalObject<kClass>{AdoptLocal{}, Fake<jobject>(2)},
+                      LocalObject<kClass>{AdoptLocal{}, Fake<jobject>(3)}};
+
+  EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(3));
+  EXPECT_CALL(*env_, GetObjectArrayElement)
+      .WillOnce(Return(Fake<jobject>(1)))
+      .WillOnce(Return(Fake<jobject>(2)))
+      .WillOnce(Return(Fake<jobject>(3)));
+
+  LocalArray<jobject, 1, kClass> new_array{AdoptLocal{}, Fake<jobjectArray>()};
+  ArrayView array_view = new_array.Pin();
+  EXPECT_TRUE(
+      std::equal(array_view.begin(), array_view.end(), expected.begin()));
+}
+
+TEST_F(JniTest, 2D_Iterates) {
+  int a[5] = {1, 2, 3, 4, 5};
+  std::array expected{1, 2, 3, 4, 5};
+
+  EXPECT_CALL(*env_, FindClass(StrEq("[I")));
+  EXPECT_CALL(*env_, NewObjectArray(5, _, Fake<jobjectArray>()));
+  EXPECT_CALL(*env_, GetArrayLength)
+      .WillOnce(Return(5))   // outer
+      .WillOnce(Return(1))   // inner: 1, running sum: 1
+      .WillOnce(Return(2))   // inner: 1, running sum: 3
+      .WillOnce(Return(3))   // inner: 1, running sum: 6
+      .WillOnce(Return(4))   // inner: 1, running sum: 10
+      .WillOnce(Return(5));  // inner: 1, running sum: 15
+
+  // 5: Once per outer array element.
+  EXPECT_CALL(*env_, GetObjectArrayElement)
+      .WillOnce(Return(Fake<jintArray>(1)))
+      .WillOnce(Return(Fake<jintArray>(2)))
+      .WillOnce(Return(Fake<jintArray>(3)))
+      .WillOnce(Return(Fake<jintArray>(4)))
+      .WillOnce(Return(Fake<jintArray>(5)));
+
+  // All the returned intArrays are deleted.
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>()));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(1)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(2)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(3)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(4)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(5)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>()));
+  EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobjectArray>())));
+
+  // 5 (outer array length) * 2 (pins per) = 10
+  EXPECT_CALL(*env_, GetIntArrayElements).Times(10).WillRepeatedly(Return(a));
+  EXPECT_CALL(*env_, ReleaseIntArrayElements).Times(10);
+
+  LocalArray<jint, 2> new_array{5, Fake<jobjectArray>()};
+
+  int sum = 0;
+  for (LocalArray<jint> arr_1d : new_array.Pin()) {
+    // Note: Each `Pin` below triggers a separate `GetIntArrayElements`.
+    {
+      auto pinn = arr_1d.Pin();
+      EXPECT_TRUE(std::equal(pinn.begin(), pinn.end(), expected.begin()));
+    }
+
+    // Note: GetArrayLength is not called again (it's cached).
+    {
+      for (jint val : arr_1d.Pin()) {
+        sum += val;
+      }
+    }
+  }
+
+  // 1 + 3 + 6 + 10 + 15 = 35
+  EXPECT_EQ(sum, 35);
+}
+
+// Identical to above except with raw loops.
+TEST_F(JniTest, 2D_Iterates_Raw_loops) {
+  int a[5] = {1, 2, 3, 4, 5};
+  std::array expected{1, 2, 3, 4, 5};
+
+  EXPECT_CALL(*env_, FindClass(StrEq("[I")));
+  EXPECT_CALL(*env_, NewObjectArray(5, _, Fake<jobjectArray>()));
+  EXPECT_CALL(*env_, GetArrayLength)
+      .WillOnce(Return(5))   // outer
+      .WillOnce(Return(1))   // inner: 1, running sum: 1
+      .WillOnce(Return(2))   // inner: 1, running sum: 3
+      .WillOnce(Return(3))   // inner: 1, running sum: 6
+      .WillOnce(Return(4))   // inner: 1, running sum: 10
+      .WillOnce(Return(5));  // inner: 1, running sum: 15
+
+  // 5: Once per outer array element.
+  EXPECT_CALL(*env_, GetObjectArrayElement)
+      .WillOnce(Return(Fake<jintArray>(1)))
+      .WillOnce(Return(Fake<jintArray>(2)))
+      .WillOnce(Return(Fake<jintArray>(3)))
+      .WillOnce(Return(Fake<jintArray>(4)))
+      .WillOnce(Return(Fake<jintArray>(5)));
+
+  // All the returned intArrays are deleted.
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(1)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(2)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(3)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(4)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jintArray>(5)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>()));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>()));
+
+  // 5 (outer array length) * 2 (pins per) = 10
+  EXPECT_CALL(*env_, GetIntArrayElements).Times(10).WillRepeatedly(Return(a));
+  EXPECT_CALL(*env_, ReleaseIntArrayElements).Times(10);
+
+  LocalArray<jint, 2> new_array{5, Fake<jobjectArray>()};
+
+  int sum = 0;
+  int loop_size = new_array.Length();
+  for (int i = 0; i < loop_size; ++i) {
+    LocalArray<jint, 1> arr_1d{new_array.Get(i)};
+
+    // Note: Each `Pin` below triggers a separate `GetIntArrayElements`.
+    {
+      auto pinn = arr_1d.Pin();
+      EXPECT_TRUE(std::equal(pinn.begin(), pinn.end(), expected.begin()));
+    }
+
+    // Note: GetArrayLength is not called again (it's cached).
+    {
+      for (jint val : arr_1d.Pin()) {
+        sum += val;
+      }
+    }
+  }
+
+  // 1 + 3 + 6 + 10 + 15 = 35
+  EXPECT_EQ(sum, 35);
+}
+
+// Identical to above except with object loops.
+TEST_F(JniTest, 2D_Iterates_Raw_loops_of_Objects) {
+  EXPECT_CALL(*env_, FindClass(StrEq("[LkClass;")));
+  EXPECT_CALL(*env_, NewObjectArray(5, _, Fake<jobjectArray>(100)));
+  EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(5));  // outer
+
+  EXPECT_CALL(*env_, GetObjectArrayElement)
+      .WillOnce(Return(Fake<jobjectArray>(1)))
+      .WillOnce(Return(Fake<jobjectArray>(2)))
+      .WillOnce(Return(Fake<jobjectArray>(3)))
+      .WillOnce(Return(Fake<jobjectArray>(4)))
+      .WillOnce(Return(Fake<jobjectArray>(5)));
+
+  // All the returned objectArrays are deleted.
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>()));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>(1)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>(2)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>(3)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>(4)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>(5)));
+
+  // Note: This is just 0 (default), not 100. 100 is the sample (i.e. template)
+  // object, no arg is the default that is created.
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobjectArray>()));
+  EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobjectArray>())));
+
+  LocalArray<jobject, 2, kClass> new_array{5, Fake<jobjectArray>(100)};
+  EXPECT_EQ(jobject{new_array}, Fake<jobjectArray>());
+
+  int i = 1;
+  for (LocalArray<jobject, 1, kClass> arr : new_array.Pin()) {
+    EXPECT_EQ(jobject(arr), Fake<jobjectArray>(i));
+    ++i;
+  }
+}
+
+#endif  // __clang__
+
+}  // namespace
diff --git a/implementation/legacy/local_array_method_multidimensional_test.cc b/implementation/legacy/local_array_method_multidimensional_test.cc
new file mode 100644
index 00000000..7428b006
--- /dev/null
+++ b/implementation/legacy/local_array_method_multidimensional_test.cc
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <type_traits>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+namespace {
+
+#if __clang__
+
+using ::jni::Array;
+using ::jni::Class;
+using ::jni::Fake;
+using ::jni::LocalArray;
+using ::jni::LocalObject;
+using ::jni::Method;
+using ::jni::Params;
+using ::jni::Rank;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::StrEq;
+
+static constexpr Class kClass2{"kClass2"};
+
+////////////////////////////////////////////////////////////////////////////////
+// As Return.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, V_2I) {
+  static constexpr Class kClass{"kClass",
+                                Method{"I", jni::Return{Array<jint, 2>{}}}};
+
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("I"), StrEq("()[[I")));
+
+  LocalObject<kClass> obj{Fake<jobject>()};
+  obj("I");
+}
+
+TEST_F(JniTest, V_2LkClass) {
+  static constexpr Class kClass{
+      "kClass", Method{"Foo", jni::Return{Array{kClass2, Rank<2>{}}}}};
+
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("()[[LkClass2;")));
+
+  LocalObject<kClass> obj{Fake<jobject>()};
+  obj("Foo");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Complex: Arrays in Params & Return.
+////////////////////////////////////////////////////////////////////////////////
+static constexpr Class kArrClass{
+    "ArrClass",
+    Method{"Foo", jni::Return{Array{jint{}}}, Params{Array<jint, 2>{}}},
+    Method{"Baz", jni::Return{Array{kClass2}}, Params{Array<jint, 3>{}}},
+    Method{"Bar", jni::Return{Array{Class{"kClass3"}, Rank<2>{}}},
+           Params{Array<jint, 3>{}, Array{double{}}}},
+};
+
+TEST_F(JniTest, 3I1D_2LkClass) {
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("Bar"), StrEq("([[[I[D)[[LkClass3;")));
+
+  LocalObject<kArrClass> obj{Fake<jobject>()};
+  obj("Bar", Fake<jobjectArray>(), Fake<jdoubleArray>());
+}
+
+TEST_F(JniTest, 2I_I) {
+  static constexpr Class kClass{
+      "kClass", Method{"I", jni::Return<int>{}, Params{Array<jint, 2>{}}}};
+
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("I"), StrEq("([[I)I")));
+
+  LocalObject<kClass> obj{Fake<jobject>()};
+  // obj("I", jintArray{nullptr}); // doesn't compile (good).
+  obj("I", Fake<jobjectArray>());
+  obj("I", LocalArray<jint, 2>{Fake<jobjectArray>()});
+}
+
+TEST_F(JniTest, 3I_1LkClass) {
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("([[[I)[LkClass2;")));
+
+  LocalObject<kArrClass> obj{Fake<jobject>()};
+
+  // The compiler complains of unused variable here. This would be worth digging
+  // into to understand the underlying cause (i.e. only the following fails).
+  // LocalArray<jobject, 1, kClass2> ret = obj("Baz", jobjectArray{nullptr});
+
+  // TODO(b/143908983): CTAD is failing.
+  // LocalArray ret = obj("Baz", jobjectArray{nullptr});
+
+  LocalArray<jobject, 1, kClass2> ret = obj("Baz", Fake<jobjectArray>());
+  static_assert(std::is_same_v<decltype(ret), LocalArray<jobject, 1, kClass2>>);
+}
+
+TEST_F(JniTest, 3I1D_1LkClass) {
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("Bar"), StrEq("([[[I[D)[[LkClass3;")));
+
+  LocalObject<kArrClass> obj{Fake<jobject>()};
+  obj("Bar", Fake<jobjectArray>(), Fake<jdoubleArray>());
+}
+
+#endif  // __clang__
+
+}  // namespace
diff --git a/implementation/legacy/local_array_method_test.cc b/implementation/legacy/local_array_method_test.cc
new file mode 100644
index 00000000..b5c81f48
--- /dev/null
+++ b/implementation/legacy/local_array_method_test.cc
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+namespace {
+
+#if __clang__
+
+using ::jni::Array;
+using ::jni::Class;
+using ::jni::Fake;
+using ::jni::LocalArray;
+using ::jni::LocalObject;
+using ::jni::Method;
+using ::jni::Params;
+using ::jni::Return;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::StrEq;
+
+static constexpr Class kClass2{"kClass2"};
+
+////////////////////////////////////////////////////////////////////////////////
+// As Return.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, ReturnSmokeTest) {
+  static constexpr Class kClass{
+      "kClass",
+      Method{"BooleanArray", Return{Array{jboolean{}}}},
+      Method{"ByteArray", Return{Array{jbyte{}}}},
+      Method{"CharArray", Return{Array{jchar{}}}},
+      Method{"ShortArray", Return{Array{jshort{}}}},
+      Method{"IntArray", Return{Array{jint{}}}},
+      Method{"FloatArray", Return{Array{jfloat{}}}},
+      Method{"DoubleArray", Return{Array{jdouble{}}}},
+      Method{"LongArray", Return{Array{jlong{}}}},
+      Method{"ObjectArray", Return{Array{kClass2}}},
+  };
+
+  EXPECT_CALL(*env_, CallObjectMethodV)
+      .WillOnce(testing::Return(Fake<jbooleanArray>()))
+      .WillOnce(testing::Return(Fake<jbyteArray>()))
+      .WillOnce(testing::Return(Fake<jcharArray>()))
+      .WillOnce(testing::Return(Fake<jshortArray>()))
+      .WillOnce(testing::Return(Fake<jintArray>()))
+      .WillOnce(testing::Return(Fake<jlongArray>()))
+      .WillOnce(testing::Return(Fake<jfloatArray>()))
+      .WillOnce(testing::Return(Fake<jdoubleArray>()))
+      .WillOnce(testing::Return(Fake<jobjectArray>()));
+
+  LocalObject<kClass> obj{Fake<jobject>()};
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("BooleanArray"), StrEq("()[Z")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("ByteArray"), StrEq("()[B")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("CharArray"), StrEq("()[C")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("ShortArray"), StrEq("()[S")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("IntArray"), StrEq("()[I")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("LongArray"), StrEq("()[J")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("FloatArray"), StrEq("()[F")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("DoubleArray"), StrEq("()[D")));
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("ObjectArray"), StrEq("()[LkClass2;")));
+
+  LocalArray<jboolean> bool_array{obj("BooleanArray")};
+  EXPECT_EQ((static_cast<jbooleanArray>(bool_array)), (Fake<jbooleanArray>()));
+
+  LocalArray<jbyte> byte_array{obj("ByteArray")};
+  EXPECT_EQ((static_cast<jbyteArray>(byte_array)), (Fake<jbyteArray>()));
+
+  LocalArray<jchar> char_array{obj("CharArray")};
+  EXPECT_EQ((static_cast<jcharArray>(char_array)), (Fake<jcharArray>()));
+
+  LocalArray<jshort> short_array{obj("ShortArray")};
+  EXPECT_EQ((static_cast<jshortArray>(short_array)), (Fake<jshortArray>()));
+
+  LocalArray<jint> int_array{obj("IntArray")};
+  EXPECT_EQ((static_cast<jintArray>(int_array)), (Fake<jintArray>()));
+
+  LocalArray<jlong> long_array{obj("LongArray")};
+  EXPECT_EQ((static_cast<jlongArray>(long_array)), (Fake<jlongArray>()));
+
+  LocalArray<jfloat> float_array{obj("FloatArray")};
+  EXPECT_EQ((static_cast<jfloatArray>(float_array)), (Fake<jfloatArray>()));
+
+  LocalArray<jdouble> double_array{obj("DoubleArray")};
+  EXPECT_EQ((static_cast<jdoubleArray>(double_array)), (Fake<jdoubleArray>()));
+
+  LocalArray<jobject, 1, kClass2> object_array{obj("ObjectArray")};
+  EXPECT_EQ((static_cast<jobjectArray>(object_array)), (Fake<jobjectArray>()));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// As Params.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, ParamsSmokeTest) {
+  static constexpr Class kClass{
+      "kClass",
+      Method{"BooleanArray", Return{}, Params{Array{jboolean{}}}},
+      Method{"ByteArray", Return{}, Params{Array{jbyte{}}}},
+      Method{"CharArray", Return{}, Params{Array{jchar{}}}},
+      Method{"ShortArray", Return{}, Params{Array{jshort{}}}},
+      Method{"IntArray", Return{}, Params{Array{jint{}}}},
+      Method{"FloatArray", Return{}, Params{Array{jfloat{}}}},
+      Method{"DoubleArray", Return{}, Params{Array{jdouble{}}}},
+      Method{"LongArray", Return{}, Params{Array{jlong{}}}},
+      Method{"ObjectArray", Return{}, Params{Array{kClass2}}},
+  };
+
+  LocalObject<kClass> obj{Fake<jobject>()};
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("BooleanArray"), StrEq("([Z)V")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("ByteArray"), StrEq("([B)V")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("CharArray"), StrEq("([C)V")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("ShortArray"), StrEq("([S)V")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("IntArray"), StrEq("([I)V")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("FloatArray"), StrEq("([F)V")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("DoubleArray"), StrEq("([D)V")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("LongArray"), StrEq("([J)V")));
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("ObjectArray"), StrEq("([LkClass2;)V")));
+
+  obj("BooleanArray", LocalArray<jboolean>{Fake<jbooleanArray>()});
+  obj("ByteArray", LocalArray<jbyte>{Fake<jbyteArray>()});
+  obj("CharArray", LocalArray<jchar>{Fake<jcharArray>()});
+  obj("ShortArray", LocalArray<jshort>{Fake<jshortArray>()});
+  obj("IntArray", LocalArray<jint>{Fake<jintArray>()});
+  obj("FloatArray", LocalArray<jfloat>{Fake<jfloatArray>()});
+  obj("DoubleArray", LocalArray<jdouble>{Fake<jdoubleArray>()});
+  obj("LongArray", LocalArray<jlong>{Fake<jlongArray>()});
+  obj("ObjectArray", LocalArray<jobject, 1, kClass2>{Fake<jobjectArray>()});
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// As Complex.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, ComplexSmokeTest) {
+  static constexpr Class kClass{
+      "kClass",
+      Method{"BooleanArray", Return{Array{jboolean{}}},
+             Params{Array{jboolean{}}}},
+      Method{"ByteArray", Return{Array{jbyte{}}}, Params{Array{jbyte{}}}},
+      Method{"CharArray", Return{Array{jchar{}}}, Params{Array{jchar{}}}},
+      Method{"ShortArray", Return{Array{jshort{}}}, Params{Array{jshort{}}}},
+      Method{"IntArray", Return{Array{jint{}}}, Params{Array{jint{}}}},
+      Method{"FloatArray", Return{Array{jfloat{}}}, Params{Array{jfloat{}}}},
+      Method{"DoubleArray", Return{Array{jdouble{}}}, Params{Array{jdouble{}}}},
+      Method{"LongArray", Return{Array{jlong{}}}, Params{Array{jlong{}}}},
+      Method{"ObjectArray", Return{Array{kClass2}}, Params{Array{kClass2}}},
+  };
+
+  EXPECT_CALL(*env_, CallObjectMethodV)
+      .WillOnce(testing::Return(Fake<jbooleanArray>()))
+      .WillOnce(testing::Return(Fake<jbyteArray>()))
+      .WillOnce(testing::Return(Fake<jcharArray>()))
+      .WillOnce(testing::Return(Fake<jshortArray>()))
+      .WillOnce(testing::Return(Fake<jintArray>()))
+      .WillOnce(testing::Return(Fake<jfloatArray>()))
+      .WillOnce(testing::Return(Fake<jdoubleArray>()))
+      .WillOnce(testing::Return(Fake<jlongArray>()))
+      .WillOnce(testing::Return(Fake<jobjectArray>()));
+
+  LocalObject<kClass> obj{Fake<jobject>()};
+
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("BooleanArray"), StrEq("([Z)[Z")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("ByteArray"), StrEq("([B)[B")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("CharArray"), StrEq("([C)[C")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("ShortArray"), StrEq("([S)[S")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("IntArray"), StrEq("([I)[I")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("FloatArray"), StrEq("([F)[F")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("DoubleArray"), StrEq("([D)[D")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("LongArray"), StrEq("([J)[J")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("ObjectArray"),
+                                 StrEq("([LkClass2;)[LkClass2;")));
+
+  LocalArray<jboolean> bool_array{
+      obj("BooleanArray", LocalArray<jboolean>{Fake<jbooleanArray>()})};
+  EXPECT_EQ((static_cast<jbooleanArray>(bool_array)), (Fake<jbooleanArray>()));
+
+  LocalArray<jbyte> byte_array{
+      obj("ByteArray", LocalArray<jbyte>{Fake<jbyteArray>()})};
+  EXPECT_EQ((static_cast<jbyteArray>(byte_array)), (Fake<jbyteArray>()));
+
+  LocalArray<jchar> char_array{
+      obj("CharArray", LocalArray<jchar>{Fake<jcharArray>()})};
+  EXPECT_EQ((static_cast<jcharArray>(char_array)), (Fake<jcharArray>()));
+
+  LocalArray<jshort> short_array{
+      obj("ShortArray", LocalArray<jshort>{Fake<jshortArray>()})};
+  EXPECT_EQ((static_cast<jshortArray>(short_array)), (Fake<jshortArray>()));
+
+  LocalArray<jint> int_array{
+      obj("IntArray", LocalArray<jint>{Fake<jintArray>()})};
+  EXPECT_EQ((static_cast<jintArray>(int_array)), (Fake<jintArray>()));
+
+  LocalArray<jfloat> float_array{
+      obj("FloatArray", LocalArray<jfloat>{Fake<jfloatArray>()})};
+  EXPECT_EQ((static_cast<jfloatArray>(float_array)), (Fake<jfloatArray>()));
+
+  LocalArray<jdouble> double_array{
+      obj("DoubleArray", LocalArray<jdouble>{Fake<jdoubleArray>()})};
+  EXPECT_EQ((static_cast<jdoubleArray>(double_array)), (Fake<jdoubleArray>()));
+
+  LocalArray<jlong> long_array{
+      obj("LongArray", LocalArray<jlong>{Fake<jlongArray>()})};
+  EXPECT_EQ((static_cast<jlongArray>(long_array)), (Fake<jlongArray>()));
+
+  LocalArray<jobject, 1, kClass2> object_array{obj(
+      "ObjectArray", LocalArray<jobject, 1, kClass2>{Fake<jobjectArray>()})};
+  EXPECT_EQ((static_cast<jobjectArray>(object_array)), (Fake<jobjectArray>()));
+}
+
+#endif  // __clang__
+
+}  // namespace
diff --git a/implementation/legacy/local_array_multidimensional_test.cc b/implementation/legacy/local_array_multidimensional_test.cc
new file mode 100644
index 00000000..160b016a
--- /dev/null
+++ b/implementation/legacy/local_array_multidimensional_test.cc
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstddef>
+#include <utility>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+namespace {
+
+#if __clang__
+
+using ::jni::AdoptLocal;
+using ::jni::Class;
+using ::jni::Fake;
+using ::jni::LocalArray;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::StrEq;
+
+static constexpr Class kClass{"kClass"};
+
+////////////////////////////////////////////////////////////////////////////////
+// Construction.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, BuildsFromSizeForMultiDimensional_no_value) {
+  EXPECT_CALL(*env_, NewObjectArray(10, _, Fake<jintArray>()));
+
+  LocalArray<jint, 2>{std::size_t{10}, Fake<jintArray>()};
+}
+
+TEST_F(JniTest, BuildsFromSizeForMultiDimensional_primitive_xref) {
+  EXPECT_CALL(*env_, FindClass(StrEq("[I")));
+  EXPECT_CALL(*env_, NewObjectArray(10, _, Fake<jintArray>()));
+
+  LocalArray<jint, 2>{std::size_t{10},
+                      LocalArray<jint>{AdoptLocal{}, Fake<jintArray>()}};
+}
+
+TEST_F(JniTest, BuildsFromSizeForMultiDimensional_primitive_lvalue) {
+  EXPECT_CALL(*env_, NewObjectArray(10, _, Fake<jintArray>()));
+
+  LocalArray<jint> arr{AdoptLocal{}, Fake<jintArray>()};
+  LocalArray<jint, 2>{std::size_t{10}, arr};
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Getters.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, GetsIntValues) {
+  EXPECT_CALL(*env_, GetObjectArrayElement(Fake<jobjectArray>(), 0))
+      .WillOnce(::testing::Return(Fake<jintArray>(0)));
+  EXPECT_CALL(*env_, GetObjectArrayElement(Fake<jobjectArray>(), 1))
+      .WillOnce(::testing::Return(Fake<jintArray>(1)));
+  EXPECT_CALL(*env_, GetObjectArrayElement(Fake<jobjectArray>(), 2))
+      .WillOnce(::testing::Return(Fake<jintArray>(2)));
+  LocalArray<jint, 2> arr{std::size_t{10}, Fake<jobjectArray>()};
+
+  EXPECT_EQ((static_cast<jintArray>(arr.Get(0))), (Fake<jintArray>(0)));
+  EXPECT_EQ((static_cast<jintArray>(arr.Get(1))), (Fake<jintArray>(1)));
+  EXPECT_EQ((static_cast<jintArray>(arr.Get(2))), (Fake<jintArray>(2)));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Setters.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, SetsIntValues) {
+  EXPECT_CALL(
+      *env_, SetObjectArrayElement(Fake<jobjectArray>(), 0, Fake<jintArray>()));
+  EXPECT_CALL(
+      *env_, SetObjectArrayElement(Fake<jobjectArray>(), 1, Fake<jintArray>()));
+  EXPECT_CALL(
+      *env_, SetObjectArrayElement(Fake<jobjectArray>(), 2, Fake<jintArray>()));
+
+  LocalArray<jint, 1> array_arg{AdoptLocal{}, Fake<jintArray>()};
+  LocalArray<jint, 2> arr{std::size_t{10}, Fake<jobjectArray>()};
+  arr.Set(0, array_arg);
+  arr.Set(1, array_arg);
+  arr.Set(2, std::move(array_arg));
+}
+
+TEST_F(JniTest, SetsObjectValues) {
+  EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(1), 0,
+                                           Fake<jobjectArray>(2)));
+  EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(1), 1,
+                                           Fake<jobjectArray>(2)));
+  EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(1), 2,
+                                           Fake<jobjectArray>(2)));
+
+  LocalArray<jobject, 1, kClass> array_arg{AdoptLocal{}, Fake<jobjectArray>(2)};
+  LocalArray<jint, 2> arr{AdoptLocal{}, Fake<jobjectArray>(1)};
+  arr.Set(0, array_arg);
+  arr.Set(1, array_arg);
+  arr.Set(2, std::move(array_arg));
+}
+
+#endif  // __clang__
+
+}  // namespace
diff --git a/implementation/legacy/local_array_string_test.cc b/implementation/legacy/local_array_string_test.cc
new file mode 100644
index 00000000..2f2ff8c9
--- /dev/null
+++ b/implementation/legacy/local_array_string_test.cc
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <utility>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+#if __clang__
+
+using ::jni::Array;
+using ::jni::ArrayStrip_t;
+using ::jni::CDecl_t;
+using ::jni::Class;
+using ::jni::Fake;
+using ::jni::Field;
+using ::jni::kJavaLangString;
+using ::jni::LocalArray;
+using ::jni::LocalObject;
+using ::jni::LocalString;
+using ::jni::Method;
+using ::jni::Params;
+using ::jni::Rank;
+using ::jni::RegularToArrayTypeMap_t;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::Return;
+using ::testing::StrEq;
+
+namespace {
+
+static constexpr Class kClass{"kClass"};
+
+////////////////////////////////////////////////////////////////////////////////
+// Strings are unusual in that they have their own type (jstring) but are
+// almost completely objects otherwise.
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+// Construction / Materialisation.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, Array_ConstructsFromAnotherStringArray) {
+  LocalArray<jstring> arr_1{Fake<jobjectArray>()};
+  LocalArray<jstring> arr_2{std::move(arr_1)};
+}
+
+TEST_F(JniTest, Array_CorrectSignatureForStringParams) {
+  EXPECT_CALL(*env_, FindClass(StrEq("ClassThatReturnsArrays")));
+  EXPECT_CALL(*env_, FindClass(StrEq("java/lang/String")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"),
+                                 StrEq("([Ljava/lang/String;)V")));
+
+  static constexpr Class kClass{
+      "ClassThatReturnsArrays",
+      Method{"StringArray", jni::Return{}, jni::Params{Array{jstring{}}}},
+  };
+
+  LocalObject<kClass> obj{jobject{nullptr}};
+  LocalArray<jstring> arr{3};
+  obj("StringArray", arr);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Caches Length.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, Array_CachesLengthExactlyOnceOnFirstRequest) {
+  EXPECT_CALL(*env_, GetArrayLength).Times(0);
+
+  LocalArray<jint> obj{Fake<jintArray>()};
+
+  EXPECT_CALL(*env_, GetArrayLength).Times(1).WillOnce(Return(5));
+  EXPECT_EQ(obj.Length(), 5);
+  EXPECT_EQ(obj.Length(), 5);
+  EXPECT_EQ(obj.Length(), 5);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Setters.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, Array_LocalStringRValuesCanBeSet) {
+  EXPECT_CALL(*env_, DeleteLocalRef(_))
+      .Times(1);  // array (object is moved from)
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>()));  // FindClass
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jstring>())).Times(0);
+
+  LocalArray<jstring> arr{3};
+  arr.Set(0, LocalString{Fake<jstring>()});
+}
+
+TEST_F(JniTest, Array_LocalVanillaObjectRValuesCanBeSet) {
+  // Unfortunately this is getting cached separately by `LocalArray`.
+  // In the future, this should drop to 1.
+  EXPECT_CALL(*env_, FindClass(StrEq("java/lang/String"))).Times(2);
+
+  EXPECT_CALL(*env_, DeleteLocalRef(_)).Times(2);  // array, in place obj
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>())).Times(2);  // FindClass
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jstring>())).Times(0);
+
+  LocalArray<jobject, 1, kJavaLangString> arr{
+      3, LocalObject<kJavaLangString>{"Foo"}};
+  arr.Set(0, LocalObject<kJavaLangString>{Fake<jstring>()});
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// As Return.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, Array_CorrectReturnSignatureForStrings) {
+  static constexpr Class kClass{
+      "kClass",
+      Method{"StringArray", jni::Return{Array{jstring{}}}},
+  };
+
+  LocalObject<kClass> obj{Fake<jobject>()};
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"),
+                                 StrEq("()[Ljava/lang/String;")));
+  LocalArray<jstring> arr = obj("StringArray");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// As Param.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, Array_CorrectParamSignatureForStrings) {
+  static constexpr Class kClass{
+      "kClass",
+      Method{"StringArray", jni::Return{}, Params{Array{jstring{}}}},
+  };
+
+  LocalObject<kClass> obj{Fake<jobject>()};
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"),
+                                 StrEq("([Ljava/lang/String;)V")));
+  LocalArray<jstring> arr{2};
+  obj("StringArray", arr);
+}
+
+TEST_F(JniTest, LocalStringArray_ConstructsObjectsForLValues) {
+  // Unlike POD, objects are constructed with a size, a jclass, and an init
+  // object.  This makes for a slightly different API then other objects.
+  EXPECT_CALL(*env_, NewObjectArray);
+
+  LocalObject<kClass> default_object{};
+  LocalArray<jobject, 1, kClass> local_object_array{5, default_object};
+}
+
+/*
+// TODO(b/143908983): Restore after fixing ubsan failures.
+TEST_F(JniTest, Array_StringsCanBeSetOnLocalString) {
+  EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(), 0, _));
+  EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(), 1, _));
+  EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(), 2, _));
+  EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(), 3, _));
+  EXPECT_CALL(*env_, SetObjectArrayElement(Fake<jobjectArray>(), 4, _));
+
+  const char* kFoo = "kFoo";
+  const std::string kNar = "kNar";
+
+  LocalArray<jstring> arr{5, LocalObject<kJavaLangString>{"Foo"}};
+  arr.Set(0, "Bar");
+  arr.Set(1, std::string{"Baz"});
+  arr.Set(2, std::string_view{"Bar"});
+  arr.Set(3, std::string_view{kFoo});
+  arr.Set(4, std::string_view{kNar});
+}
+*/
+
+////////////////////////////////////////////////////////////////////////////////
+// String Fields.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, Array_CorrectFieldSignatureForStrings) {
+  static constexpr Class kClass{
+      "kClass",
+      Field{"StringArrayRank1", Array{jstring{}}},
+      Field{"StringArrayRank2", Array{jstring{}, Rank<2>{}}},
+      Field{"StringArrayRank3", Array{jstring{}, Rank<3>{}}},
+  };
+
+  LocalObject<kClass> obj{jobject{nullptr}};
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("StringArrayRank1"),
+                                StrEq("[Ljava/lang/String;")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("StringArrayRank2"),
+                                StrEq("[[Ljava/lang/String;")));
+  EXPECT_CALL(*env_, GetFieldID(_, StrEq("StringArrayRank3"),
+                                StrEq("[[[Ljava/lang/String;")));
+
+  LocalArray<jstring> arr1 = obj["StringArrayRank1"].Get();
+  LocalArray<jstring, 2> arr2 = obj["StringArrayRank2"].Get();
+  LocalArray<jstring, 3> arr3 = obj["StringArrayRank3"].Get();
+}
+
+}  // namespace
+
+#endif  // __clang__
diff --git a/implementation/legacy/local_object_test.cc b/implementation/legacy/local_object_test.cc
new file mode 100644
index 00000000..beee5b26
--- /dev/null
+++ b/implementation/legacy/local_object_test.cc
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <array>
+#include <tuple>
+#include <utility>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+#if __clang__
+
+namespace {
+
+using ::jni::Class;
+using ::jni::Fake;
+using ::jni::Field;
+using ::jni::LocalObject;
+using ::jni::Method;
+using ::jni::NewRef;
+using ::jni::Params;
+using ::jni::test::AsNewLocalReference;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::StrEq;
+
+static constexpr Class kClass{"kClass"};
+static constexpr Class kClass2{"kClass2"};
+
+TEST_F(JniTest, LocalObject_AllowsNullPtrT) {
+  EXPECT_CALL(*env_, NewLocalRef).Times(0);
+  EXPECT_CALL(*env_, DeleteLocalRef).Times(0);
+
+  LocalObject<kClass> obj{nullptr};
+  EXPECT_EQ(jobject{obj}, nullptr);
+}
+
+TEST_F(JniTest, LocalObject_DoesntTryToDeleteNull) {
+  EXPECT_CALL(*env_, NewLocalRef).Times(0);
+  EXPECT_CALL(*env_, DeleteLocalRef).Times(0);
+
+  LocalObject<kClass> obj{jobject{nullptr}};
+  EXPECT_EQ(jobject{obj}, nullptr);
+}
+
+TEST_F(JniTest, LocalObject_CallsNewAndDeleteOnNewObject) {
+  EXPECT_CALL(*env_, NewObjectV).WillOnce(testing::Return(Fake<jobject>()));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>())).Times(1);
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>())).Times(1);
+
+  LocalObject<kClass> obj{};
+  EXPECT_EQ(jobject{obj}, Fake<jobject>());
+}
+
+TEST_F(JniTest, LocalObject_CallsOnlyDeleteOnWrapCtor) {
+  EXPECT_CALL(*env_, NewLocalRef).Times(1);
+  EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobject>())))
+      .Times(1);
+
+  LocalObject<kClass> obj{Fake<jobject>()};
+  EXPECT_NE(jobject{obj}, nullptr);
+}
+
+TEST_F(JniTest, LocalObject_CallsNewLocalRefByDefault) {
+  EXPECT_CALL(*env_, NewLocalRef).WillOnce(::testing::Return(Fake<jobject>(2)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(2)));
+
+  LocalObject<kClass> obj{Fake<jobject>(1)};
+  EXPECT_EQ(jobject{obj}, Fake<jobject>(2));
+}
+
+TEST_F(JniTest, LocalObject_CallsNewLocalRefOnCopy) {
+  EXPECT_CALL(*env_, NewLocalRef).WillOnce(::testing::Return(Fake<jobject>(2)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(2)));
+
+  LocalObject<kClass> obj{NewRef{}, Fake<jobject>(1)};
+  EXPECT_EQ(jobject{obj}, Fake<jobject>(2));
+}
+
+TEST_F(JniTest, LocalObject_ObjectReturnsInstanceMethods) {
+  // This test doesn't use the default JniTest helpers to be a little more
+  // explicit about exactly what calls must be made in what order.
+  static constexpr Class kClass{
+      "com/google/AnotherClass",
+      Method{"Foo", jni::Return<jint>{}, Params<jint>{}},
+      Method{"Baz", jni::Return<void>{}, Params<jfloat>{}},
+      Method{"AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplates"
+             "ToHandle",
+             jni::Return<jdouble>{},
+             Params<jint, jfloat, jint, jfloat, jdouble>{}}};
+
+  InSequence seq;
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>())).Times(1);
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("<init>"), StrEq("()V")))
+      .WillOnce(testing::Return(Fake<jmethodID>(1)));
+  EXPECT_CALL(*env_, NewObjectV(_, Fake<jmethodID>(1), _))
+      .WillOnce(testing::Return(Fake<jobject>()));
+
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)I")))
+      .WillOnce(testing::Return(Fake<jmethodID>(2)));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("(F)V")))
+      .WillOnce(testing::Return(Fake<jmethodID>(2)));
+  EXPECT_CALL(*env_,
+              GetMethodID(_,
+                          StrEq("AMethodWithAReallyLongNameThatWouldPossiblyBeH"
+                                "ardForTemplatesToHandle"),
+                          StrEq("(IFIFD)D")))
+      .WillOnce(testing::Return(Fake<jmethodID>(2)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>())).Times(1);
+
+  LocalObject<kClass> obj{};
+  obj("Foo", 12345);
+  obj("Baz", 12345.f);
+  obj("AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplatesToHandle",
+      12345, 12345.f, 12345, 12345.f, jdouble{12345});
+}
+
+TEST_F(JniTest, LocalObject_CallsDeleteOnceAfterAMoveConstruction) {
+  EXPECT_CALL(*env_, NewLocalRef).Times(1);
+  EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobject>())))
+      .Times(1);
+
+  LocalObject<kClass> obj_1{Fake<jobject>()};
+
+  EXPECT_NE(jobject{obj_1}, nullptr);
+
+  LocalObject<kClass> obj_2{std::move(obj_1)};
+
+  EXPECT_NE(jobject{obj_2}, nullptr);
+}
+
+TEST_F(JniTest, LocalObject_FunctionsProperlyInSTLContainer) {
+  EXPECT_CALL(*env_, NewLocalRef).Times(2);
+  EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobject>(1))));
+  EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobject>(2))));
+
+  LocalObject<kClass> obj_1{Fake<jobject>(1)};
+  LocalObject<kClass> obj_2{Fake<jobject>(2)};
+  std::tuple t{std::move(obj_1), std::move(obj_2)};
+}
+
+TEST_F(JniTest, LocalObject_ValuesWorkAfterMoveConstructor) {
+  static constexpr Class kClass{
+      "com/google/ValuesWorkAfterMoveConstructor",
+      Method{"Foo", jni::Return<jint>{}, Params<jint>{}},
+      Field{"BarField", jint{}}};
+
+  EXPECT_CALL(*env_, CallIntMethodV).Times(3);
+  EXPECT_CALL(*env_, SetIntField).Times(4);
+
+  LocalObject<kClass> obj_1{Fake<jobject>()};
+  obj_1("Foo", 1);
+  obj_1("Foo", 2);
+  obj_1["BarField"].Set(1);
+
+  LocalObject<kClass> obj_2{std::move(obj_1)};
+  obj_2("Foo", 3);
+  obj_2["BarField"].Set(2);
+  obj_2["BarField"].Set(3);
+  obj_2["BarField"].Set(4);
+}
+
+TEST_F(JniTest, LocalObject_ReleasesLocalsForAlternateConstructors) {
+  static constexpr Class kClass{"ReleasesLocalsForAlternateConstructors",
+                                jni::Constructor<int>{}};
+  LocalObject<kClass> g1{1};
+  LocalObject<kClass> g2{2};
+  LocalObject<kClass> g3{3};
+  EXPECT_CALL(*env_, DeleteLocalRef(_)).Times(3);
+}
+
+TEST_F(JniTest, LocalObject_ComparesAgainstOtherLocalObjects) {
+  LocalObject<kClass> val_1{Fake<jobject>(1)};
+  LocalObject<kClass2> val_2{Fake<jobject>(2)};
+
+  EXPECT_TRUE(val_1 == val_1);
+  EXPECT_FALSE(val_1 == val_2);
+  EXPECT_TRUE(val_1 != val_2);
+  EXPECT_TRUE(val_2 == val_2);
+  EXPECT_TRUE(val_2 != val_1);
+  EXPECT_FALSE(val_1 == val_2);
+}
+
+TEST_F(JniTest, LocalObject_ComparesAgainstjobjects) {
+  static constexpr Class kClass{"kClass1"};
+  LocalObject<kClass> val_1{Fake<jobject>()};
+
+  EXPECT_TRUE(val_1 == AsNewLocalReference(Fake<jobject>()));
+  EXPECT_TRUE(AsNewLocalReference(Fake<jobject>()) == val_1);
+
+  EXPECT_FALSE(val_1 != AsNewLocalReference(Fake<jobject>()));
+  EXPECT_FALSE(AsNewLocalReference(Fake<jobject>()) != val_1);
+}
+
+struct A {
+  LocalObject<kClass> val_1;
+  LocalObject<kClass2> val_2;
+
+  A(LocalObject<kClass>&& val_1, LocalObject<kClass2>&& val_2)
+      : val_1(std::move(val_1)), val_2(std::move(val_2)) {}
+
+  A(A&& rhs) : val_1(std::move(rhs.val_1)), val_2(std::move(rhs.val_2)) {}
+};
+
+constexpr bool operator==(const A& lhs, const A& rhs) {
+  return lhs.val_1 == rhs.val_1 && lhs.val_2 == rhs.val_2;
+}
+
+constexpr bool operator!=(const A& lhs, const A& rhs) {
+  return lhs.val_1 != rhs.val_1 || lhs.val_2 != rhs.val_2;
+}
+
+TEST_F(JniTest, LocalObject_ComparesAgainstOtherLocalObjects_InContainers) {
+  A val_1{LocalObject<kClass>{Fake<jobject>(1)}, {Fake<jobject>(2)}};
+  A val_2{LocalObject<kClass2>{Fake<jobject>(1)}, {Fake<jobject>(3)}};
+
+  EXPECT_FALSE(val_1 == val_2);
+  EXPECT_TRUE(val_1 != val_2);
+
+  EXPECT_EQ((std::array{A{LocalObject<kClass>(Fake<jobject>(1)),
+                          LocalObject<kClass2>(Fake<jobject>(2))}}),
+            (std::array{A{LocalObject<kClass>(Fake<jobject>(1)),
+                          LocalObject<kClass2>(Fake<jobject>(2))}}));
+
+  EXPECT_TRUE((std::array{A{LocalObject<kClass>(Fake<jobject>(1)),
+                            LocalObject<kClass2>(Fake<jobject>(2))}} !=
+               (std::array{A{LocalObject<kClass>(Fake<jobject>(1)),
+                             LocalObject<kClass2>(Fake<jobject>(3))}})));
+}
+
+TEST_F(JniTest, LocalObject_SupportsPassingAnObjectAsAnLvalue) {
+  static constexpr Class kClass2{
+      "Class2", Method{"Foo", jni::Return{}, jni::Params{kClass}}};
+
+  LocalObject<kClass> a{};
+  LocalObject<kClass2> b{};
+  b("Foo", a);
+}
+
+TEST_F(JniTest, LocalObject_SupportsReturningAClass) {
+  static constexpr Class kClass{
+      "Class1", Method{"Foo", jni::Return{kClass2}, jni::Params{}}};
+
+  LocalObject<kClass> a{};
+  a("Foo");
+}
+
+TEST_F(JniTest, LocalObject_SupportsReturningAString) {
+  static constexpr Class kClass{
+      "Class1", Method{"Foo", jni::Return<jstring>{}, jni::Params{}}};
+
+  LocalObject<kClass> a{};
+  a("Foo");
+}
+
+jobject ReturnOutputOfMethod() {
+  static constexpr Class kClass2{"Class2", Method{"Foo", jni::Return{kClass}}};
+
+  return LocalObject<kClass2>{}("Foo").Release();
+}
+
+TEST_F(JniTest, LocalObject_CompilesWhenReturnReleasing) {
+  ReturnOutputOfMethod();
+}
+
+TEST_F(JniTest, LocalObject_SupportsPassingAnObjectAsAnPrvalue) {
+  static constexpr Class kClass2{
+      "Class2", Method{"Foo", jni::Return{}, jni::Params{kClass}}};
+
+  LocalObject<kClass> a{};
+  LocalObject<kClass2> b{};
+  b("Foo", std::move(a));
+}
+
+TEST_F(JniTest, LocalObject_SupportsPassingAnObjectAsAnXvalue) {
+  static constexpr Class kClass2{
+      "Class2", Method{"Foo", jni::Return{}, jni::Params{kClass}}};
+
+  LocalObject<kClass2> b{};
+  b("Foo", LocalObject<kClass>{});
+}
+
+TEST_F(JniTest, LocalObject_MovesInContainerStruct) {
+  struct A {
+    const LocalObject<kClass> val;
+
+    A(LocalObject<kClass>&& in) : val(std::move(in)) {}
+  };
+
+  EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobject>())));
+
+  A a{LocalObject<kClass>{Fake<jobject>()}};
+}
+
+TEST_F(JniTest, LocalObject_DoesntCrossTalkOverClassMethodIds) {
+  static constexpr Class kClass{
+      "kClass", Method{"Foo", jni::Return{}, jni::Params<int>{}}};
+
+  static constexpr Class kClass2{
+      "kClass2", Method{"Foo", jni::Return{}, jni::Params<int>{}}};
+
+  EXPECT_CALL(*env_, GetMethodID(_, _, StrEq("(I)V"))).Times(2);
+
+  LocalObject<kClass> obj_1{Fake<jobject>(1)};
+  LocalObject<kClass2> obj_2{Fake<jobject>(2)};
+
+  // These are different method IDs (they are different classes).
+  obj_1("Foo", 1);
+  obj_2("Foo", 1);
+}
+
+}  // namespace
+
+#endif  // __clang__
\ No newline at end of file
diff --git a/implementation/legacy/method_ref_test.cc b/implementation/legacy/method_ref_test.cc
new file mode 100644
index 00000000..c5a972fd
--- /dev/null
+++ b/implementation/legacy/method_ref_test.cc
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstddef>
+#include <utility>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+#if __clang__
+
+namespace {
+
+using ::jni::AdoptGlobal;
+using ::jni::Array;
+using ::jni::Class;
+using ::jni::Fake;
+using ::jni::GlobalObject;
+using ::jni::Id;
+using ::jni::IdType;
+using ::jni::JniT;
+using ::jni::kDefaultClassLoader;
+using ::jni::kNoIdx;
+using ::jni::LocalArray;
+using ::jni::LocalObject;
+using ::jni::Method;
+using ::jni::OverloadRef;
+using ::jni::Params;
+using ::jni::Rank;
+using ::jni::Return;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::Eq;
+using ::testing::InSequence;
+using ::testing::StrEq;
+
+template <const auto& class_loader_v, const auto& class_v, size_t I>
+using MethodRefT_t = OverloadRef<Id<JniT<jobject, class_v, class_loader_v>,
+                                    IdType::OVERLOAD, I, 0, kNoIdx, 0>,
+                                 IdType::OVERLOAD_PARAM>;
+
+TEST_F(JniTest, MethodRef_DoesntStaticCrossTalkWithTagUse) {
+  static constexpr Method m{"FooV", Return<void>{}, Params{jint{}}};
+  static constexpr Class kSomeClass{"someClass", m};
+
+  MethodRefT_t<kDefaultClassLoader, kSomeClass, 0>::Invoke(
+      Fake<jclass>(), Fake<jobject>(), 123);
+}
+
+TEST_F(JniTest, MethodRef_CallsGetMethodCorrectlyForSingleMethod) {
+  static constexpr Method m1{"FooV", Return<void>{}};
+  static constexpr Class c{"SimpleClass", m1};
+
+  InSequence seq;
+  EXPECT_CALL(*env_,
+              GetMethodID(Eq(Fake<jclass>()), StrEq("FooV"), StrEq("()V")))
+      .WillOnce(testing::Return(Fake<jmethodID>()));
+  EXPECT_CALL(*env_, CallVoidMethodV(Fake<jobject>(), Fake<jmethodID>(), _));
+
+  MethodRefT_t<kDefaultClassLoader, c, 0>::Invoke(Fake<jclass>(),
+                                                  Fake<jobject>());
+}
+
+TEST_F(JniTest, MethodRef_ReturnWithObject) {
+  static constexpr Class c2{"someClass2"};
+  static constexpr Method m1{"FooV", Return{c2}};
+  static constexpr Class c{"someClass", m1};
+
+  InSequence seq;
+  EXPECT_CALL(*env_, GetMethodID(Eq(Fake<jclass>()), StrEq("FooV"),
+                                 StrEq("()LsomeClass2;")))
+      .WillOnce(testing::Return(Fake<jmethodID>()));
+  EXPECT_CALL(*env_, CallObjectMethodV(Fake<jobject>(), Fake<jmethodID>(), _));
+
+  MethodRefT_t<kDefaultClassLoader, c, 0>::Invoke(Fake<jclass>(),
+                                                  Fake<jobject>());
+}
+
+TEST_F(JniTest, MethodRef_ReturnWithRank1Object) {
+  static constexpr Class c2{"someClass2"};
+  static constexpr Method m1{"FooV", Return{Array{c2}}};
+  static constexpr Class c{"someClass", m1};
+
+  InSequence seq;
+  EXPECT_CALL(*env_, GetMethodID(Eq(Fake<jclass>()), StrEq("FooV"),
+                                 StrEq("()[LsomeClass2;")))
+      .WillOnce(testing::Return(Fake<jmethodID>()));
+  EXPECT_CALL(*env_, CallObjectMethodV(Fake<jobject>(), Fake<jmethodID>(), _));
+
+  MethodRefT_t<kDefaultClassLoader, c, 0>::Invoke(Fake<jclass>(),
+                                                  Fake<jobject>());
+}
+
+TEST_F(JniTest, MethodRef_ReturnWithRank2Object) {
+  static constexpr Class c2{"someClass2"};
+  static constexpr Method m1{"FooV", Return{Array{c2, Rank<2>{}}}, Params<>{}};
+  static constexpr Class c{"someClass", m1};
+
+  InSequence seq;
+  EXPECT_CALL(*env_, GetMethodID(Eq(Fake<jclass>()), StrEq("FooV"),
+                                 StrEq("()[[LsomeClass2;")))
+      .WillOnce(testing::Return(Fake<jmethodID>()));
+  EXPECT_CALL(*env_, CallObjectMethodV(Fake<jobject>(), Fake<jmethodID>(), _));
+
+  MethodRefT_t<kDefaultClassLoader, c, 0>::Invoke(Fake<jclass>(),
+                                                  Fake<jobject>());
+}
+
+TEST_F(JniTest, MethodRef_ReturnWithNoParams) {
+  static constexpr Method m1{"FooV", Return<void>{}};
+  static constexpr Method m2{"BarI", Return<jint>{}};
+  static constexpr Method m3{"BazF", Return<jfloat>{}};
+  static constexpr Class c{"someClass", m1, m2, m3};
+
+  InSequence seq;
+  EXPECT_CALL(*env_,
+              GetMethodID(Eq(Fake<jclass>()), StrEq("FooV"), StrEq("()V")))
+      .WillOnce(testing::Return(Fake<jmethodID>(1)));
+  EXPECT_CALL(*env_, CallVoidMethodV(Fake<jobject>(), Fake<jmethodID>(1), _));
+
+  EXPECT_CALL(*env_,
+              GetMethodID(Eq(Fake<jclass>()), StrEq("BarI"), StrEq("()I")))
+      .WillOnce(testing::Return(Fake<jmethodID>(2)));
+  EXPECT_CALL(*env_, CallIntMethodV(Fake<jobject>(), Fake<jmethodID>(2), _));
+
+  EXPECT_CALL(*env_,
+              GetMethodID(Eq(Fake<jclass>()), StrEq("BazF"), StrEq("()F")))
+      .WillOnce(testing::Return(Fake<jmethodID>(3)));
+  EXPECT_CALL(*env_, CallFloatMethodV(Fake<jobject>(), Fake<jmethodID>(3), _));
+
+  MethodRefT_t<kDefaultClassLoader, c, 0>::Invoke(Fake<jclass>(),
+                                                  Fake<jobject>());
+  MethodRefT_t<kDefaultClassLoader, c, 1>::Invoke(Fake<jclass>(),
+                                                  Fake<jobject>());
+  MethodRefT_t<kDefaultClassLoader, c, 2>::Invoke(Fake<jclass>(),
+                                                  Fake<jobject>());
+}
+
+TEST_F(JniTest, MethodRef_SingleParam) {
+  constexpr Method m1{"SomeFunc1", Return<void>{}, Params<jint>{}};
+  constexpr Method m2{"SomeFunc2", Return<jint>{}, Params<jfloat>{}};
+  constexpr Method m3{"SomeFunc3", Return<jfloat>{}, Params<jfloat>{}};
+  static constexpr Class c{"someClass", m1, m2, m3};
+
+  InSequence seq;
+  EXPECT_CALL(
+      *env_, GetMethodID(Eq(Fake<jclass>()), StrEq("SomeFunc1"), StrEq("(I)V")))
+      .WillOnce(testing::Return(Fake<jmethodID>(1)));
+  // There is no clear way to test variable vaargs type arguments using Gmock,
+  // but at least we can test the correct method is called.
+  EXPECT_CALL(*env_, CallVoidMethodV(Fake<jobject>(), Fake<jmethodID>(1), _));
+
+  EXPECT_CALL(
+      *env_, GetMethodID(Eq(Fake<jclass>()), StrEq("SomeFunc2"), StrEq("(F)I")))
+      .WillOnce(testing::Return(Fake<jmethodID>(2)));
+  EXPECT_CALL(*env_, CallIntMethodV(Fake<jobject>(), Fake<jmethodID>(2), _));
+
+  EXPECT_CALL(
+      *env_, GetMethodID(Eq(Fake<jclass>()), StrEq("SomeFunc3"), StrEq("(F)F")))
+      .WillOnce(testing::Return(Fake<jmethodID>(3)));
+  EXPECT_CALL(*env_, CallFloatMethodV(Fake<jobject>(), Fake<jmethodID>(3), _));
+
+  MethodRefT_t<kDefaultClassLoader, c, 0>::Invoke(Fake<jclass>(),
+                                                  Fake<jobject>(), 1);
+  MethodRefT_t<kDefaultClassLoader, c, 1>::Invoke(Fake<jclass>(),
+                                                  Fake<jobject>(), 1.234f);
+  MethodRefT_t<kDefaultClassLoader, c, 2>::Invoke(Fake<jclass>(),
+                                                  Fake<jobject>(), 5.6789f);
+}
+
+TEST_F(JniTest, MethodRef_ReturnsObjects) {
+  static constexpr Class c1{"Bazz"};
+  static constexpr Class kClass{
+      "com/google/ReturnsObjects",
+      Method{"Foo", Return{c1}, Params<jint>{}},
+  };
+
+  // Note, class refs are not released, so Times() != 2.
+  EXPECT_CALL(*env_, NewObjectV).WillOnce(testing::Return(Fake<jobject>()));
+
+  GlobalObject<kClass> global_object{};
+  LocalObject<c1> new_obj{global_object("Foo", 5)};
+}
+
+TEST_F(JniTest, MethodRef_PassesObjects) {
+  static constexpr Class c1{"com/google/Bazz"};
+  static constexpr Class kClass{
+      "com/google/PassesObjects",
+      Method{"Foo", Return<jint>{}, Params{c1}},
+  };
+
+  LocalObject<c1> local_object{Fake<jobject>()};
+  GlobalObject<kClass> global_object{AdoptGlobal{}, Fake<jobject>(100)};
+
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("Foo"), StrEq("(Lcom/google/Bazz;)I")));
+
+  global_object("Foo", local_object);
+}
+
+TEST_F(JniTest, MethodRef_PassesAndReturnsMultipleObjects) {
+  static constexpr Class c1{"Class1"};
+  static constexpr Class c2{"Class2"};
+  static constexpr Class c3{"Class3"};
+  static constexpr Class c4{"Class4"};
+
+  static constexpr Class class_under_test{
+      "com/google/PassesAndReturnsMultipleObjects",
+      Method{"Foo", Return{c1}, Params{c1, c2, c3, c4}},
+  };
+
+  LocalObject<c1> obj1{Fake<jobject>(1)};
+  LocalObject<c2> obj2{Fake<jobject>(2)};
+  LocalObject<c3> obj3{Fake<jobject>(3)};
+  LocalObject<c4> obj4{Fake<jobject>(4)};
+  LocalObject<class_under_test> object_under_test{Fake<jobject>(5)};
+
+  LocalObject<c1> obj5{object_under_test("Foo", obj1, obj2, obj3, obj4)};
+}
+
+TEST_F(JniTest, MethodRef_SupportsForwardDefines) {
+  static constexpr Class kClass1{
+      "kClass1",
+      Method{"m1", Return<void>{}, Params{Class{"kClass1"}}},
+      Method{"m2", Return<void>{}, Params{Class{"kClass2"}}},
+      Method{"m3", Return{Class{"kClass1"}}},
+      Method{"m4", Return{Class{"kClass2"}}},
+  };
+
+  static constexpr Class kClass2{
+      "kClass2",
+      Method{"m1", Return<void>{}, Params{Class{"kClass1"}}},
+      Method{"m2", Return<void>{}, Params{Class{"kClass2"}}},
+      Method{"m3", Return{Class{"kClass1"}}},
+      Method{"m4", Return{Class{"kClass2"}}},
+  };
+
+  LocalObject<kClass1> c1_obj1{Fake<jobject>(1)};
+  LocalObject<kClass1> c1_obj2{Fake<jobject>(2)};
+
+  LocalObject<kClass2> c2_obj1{Fake<jobject>(3)};
+  LocalObject<kClass2> c2_obj2{Fake<jobject>(4)};
+
+  c1_obj1("m1", c1_obj1);
+  c1_obj1("m2", c2_obj1);
+  c1_obj1("m1", c1_obj1("m3"));
+  c1_obj1("m2", c1_obj1("m4"));
+
+  c2_obj1("m1", c1_obj1);
+  c2_obj1("m2", c2_obj2);
+  c2_obj1("m2", std::move(std::move(c2_obj2)));
+
+  c1_obj1("m2", std::move(c2_obj1));
+
+  // c2_obj1("m1", c1_obj1); // illegal! triggers warnings (post move read).
+  // c2_obj1("m2", c2_obj2); // illegal! triggers warnings (post move read).
+  // c2_obj1("m2", std::move(c2_obj2));  // illegal! triggers warnings (post
+  // move read).
+}
+
+TEST_F(JniTest, MethodRef_SupportsStrings) {
+  static constexpr Class class_under_test{
+      "com/google/SupportsStrings",
+      Method{"Foo", Return<void>{}, Params<jstring>{}},
+      Method{"Bar", Return<void>{}, Params<jstring, jstring>{}},
+      Method{"Baz", Return<jstring>{}},
+  };
+
+  LocalObject<class_under_test> obj1{Fake<jobject>()};
+  obj1("Foo", "This is a method.");
+  obj1("Bar", "This is a method.", "It takes strings");
+  obj1("Baz");
+}
+
+TEST_F(JniTest, MethodRef_SupportsArrays) {
+  static constexpr Class kClass{"kClass"};
+  static constexpr Class class_under_test{
+      "com/google/SupportsArrays",
+      Method{"Foo", Return<void>{}, Params{Array{kClass}}},
+      Method{"Bar", Return<void>{}, Params<int>{}}};
+
+  LocalArray<jobject, 1, kClass> local_array{nullptr};
+  LocalObject<class_under_test> obj1{Fake<jobject>()};
+  obj1("Foo", local_array);
+}
+
+}  // namespace
+
+#endif  // __clang__
\ No newline at end of file
diff --git a/implementation/legacy/multi_type_test.cc b/implementation/legacy/multi_type_test.cc
new file mode 100644
index 00000000..77de5065
--- /dev/null
+++ b/implementation/legacy/multi_type_test.cc
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+#if __clang__
+
+namespace {
+
+using ::jni::Class;
+using ::jni::Fake;
+using ::jni::Field;
+using ::jni::Method;
+using ::jni::Params;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::Eq;
+using ::testing::Return;
+using ::testing::StrEq;
+
+TEST_F(JniTest, MultiTypeTest_SimpleSmokeTestForSingleObject) {
+  static constexpr Class object{
+      "ARCore",
+      Method{"Foo", jni::Return<jint>{}, Params<jint, jfloat>{}},
+      Method{"Bar", jni::Return{jint{}}},
+      Method{"Baz", jni::Return<void>{}, Params<jfloat>{}},
+      Field{"SomeField", jint{}},
+  };
+
+  EXPECT_CALL(*env_, FindClass(StrEq("ARCore")))
+      .WillOnce(Return(Fake<jclass>(1)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>(1)));
+  EXPECT_CALL(*env_, NewGlobalRef(Eq(Fake<jclass>(1))))
+      .WillOnce(Return(Fake<jclass>(2)));
+  EXPECT_CALL(*env_,
+              GetMethodID(Fake<jclass>(2), StrEq("<init>"), StrEq("()V")))
+      .WillOnce(Return(Fake<jmethodID>()));
+
+  EXPECT_CALL(*env_, NewObjectV(Fake<jclass>(2), Fake<jmethodID>(), _))
+      .WillOnce(Return(Fake<jobject>(1)));
+  EXPECT_CALL(*env_, NewGlobalRef(Fake<jobject>(1)))
+      .WillOnce(Return(Fake<jobject>(2)));
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>(1))).Times(1);
+
+  EXPECT_CALL(*env_, GetMethodID(Fake<jclass>(2), StrEq("Foo"), StrEq("(IF)I")))
+      .WillOnce(Return(Fake<jmethodID>()));
+  EXPECT_CALL(*env_, GetMethodID(Fake<jclass>(2), StrEq("Bar"), StrEq("()I")))
+      .WillOnce(Return(Fake<jmethodID>()));
+  EXPECT_CALL(*env_, GetMethodID(Fake<jclass>(2), StrEq("Baz"), StrEq("(F)V")))
+      .WillOnce(Return(Fake<jmethodID>()));
+  EXPECT_CALL(*env_,
+              GetFieldID(Fake<jclass>(2), StrEq("SomeField"), StrEq("I")))
+      .WillOnce(Return(Fake<jfieldID>()));
+
+  EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jobject>(2))).Times(1);
+  EXPECT_CALL(*env_, DeleteGlobalRef(Fake<jclass>(2))).Times(1);
+
+  jni::GlobalObject<object> obj{};
+  obj("Foo", 1, 2.f);
+  obj("Baz", 1.f);
+  obj("Baz", 1.f);
+  obj("Baz", 2.f);
+  obj("Baz", 3.f);
+  obj("Bar");
+  obj["SomeField"].Get();
+}
+
+TEST_F(JniTest, MultiTypeTest_MethodsOfSameNameButDifferentClassAreUnique) {
+  EXPECT_CALL(*env_, GetMethodID).Times(2);
+
+  static constexpr Class c1{
+      "com/google/ARCore",
+      Method{"Foo", jni::Return<jint>{}, Params<jint>{}},
+  };
+  static constexpr Class c2{
+      "com/google/VRCore",
+      Method{"Foo", jni::Return<jint>{}, Params<jint>{}},
+  };
+
+  jni::LocalObject<c1> obj1{Fake<jobject>(1)};
+  jni::LocalObject<c2> obj2{Fake<jobject>(2)};
+  obj1("Foo", 12345);
+  obj2("Foo", 12345);
+
+  // All of these calls ought not query for a method ID again.
+  obj1("Foo", 12345);
+  obj1("Foo", 12345);
+  obj1("Foo", 12345);
+  obj1("Foo", 12345);
+  obj2("Foo", 12345);
+  obj2("Foo", 12345);
+  obj2("Foo", 12345);
+
+  jni::LocalObject<c1> obj3{Fake<jobject>(3)};
+  obj3("Foo", 12345);
+  obj3("Foo", 12345);
+}
+
+}  // namespace
+
+#endif  // __clang__
\ No newline at end of file
diff --git a/implementation/legacy/overload_ref_test.cc b/implementation/legacy/overload_ref_test.cc
new file mode 100644
index 00000000..bc40c349
--- /dev/null
+++ b/implementation/legacy/overload_ref_test.cc
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "jni_bind.h"
+#include "jni_test.h"
+
+#if __clang__
+
+namespace {
+
+using ::jni::Class;
+using ::jni::LocalObject;
+using ::jni::Method;
+using ::jni::Overload;
+using ::jni::Params;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::StrEq;
+
+TEST_F(JniTest, MethodRef_AsksForCorrectMethods1) {
+  static constexpr Class kClass{
+      "com/google/SupportsStrings",
+      Method{"Foo", jni::Return<void>{}, Params<jstring>{}},
+  };
+  LocalObject<kClass> obj{};
+
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;)V")));
+
+  obj("Foo", "test");
+}
+
+TEST_F(JniTest, MethodRef_AsksForCorrectMethods2) {
+  static constexpr Class kClass{
+      "com/google/SupportsStrings",
+      Method{"Foo", Overload{jni::Return<void>{}, Params{jint{}}},
+             Overload{jni::Return<void>{}, Params{jstring{}}}}};
+  LocalObject<kClass> obj{};
+
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)V")));
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;)V")));
+
+  obj("Foo", 1);
+  // obj("Foo", 2.f); doesn't compile (good).
+  obj("Foo", "test");
+}
+
+TEST_F(JniTest, MethodRef_AsksForCorrectMethods3) {
+  static constexpr Class kClass{
+      "com/google/SupportsStrings",
+      Method{
+          "Foo",
+          Overload{jni::Return<void>{}, Params{jint{}}},
+          Overload{jni::Return<void>{}, Params{jstring{}}},
+          Overload{jni::Return<void>{}, Params{jstring{}, jstring{}}},
+      }};
+  LocalObject<kClass> obj{};
+
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)V")));
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;)V")));
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("Foo"),
+                          StrEq("(Ljava/lang/String;Ljava/lang/String;)V")));
+
+  obj("Foo", 1);
+  // obj("Foo", 2.f); // doesn't compile (good).
+  obj("Foo", "test");
+  obj("Foo", "test1", "test2");
+  obj("Foo", "this_doesnt", "trigger_method_lookup");
+}
+
+TEST_F(JniTest, MethodRef_AsksForCorrectMethodsWhenMultiplePresent4) {
+  static constexpr Class kClass{
+      "com/google/SupportsStrings",
+      Method{
+          "Foo",
+          Overload{jni::Return<void>{}, Params{jint{}}},
+          Overload{jni::Return<void>{}, Params{jstring{}}},
+          Overload{jni::Return<void>{}, Params{jstring{}, jstring{}}},
+      },
+      Method{
+          "Baz",
+          Overload{jni::Return<void>{}, Params{jint{}}},
+          Overload{jni::Return<void>{}, Params{jstring{}, jstring{}}},
+          Overload{jni::Return<void>{}, Params{jfloat{}, jfloat{}}},
+      }};
+  LocalObject<kClass> obj{};
+
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)V")));
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;)V")));
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("Foo"),
+                          StrEq("(Ljava/lang/String;Ljava/lang/String;)V")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("(I)V")));
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("Baz"),
+                          StrEq("(Ljava/lang/String;Ljava/lang/String;)V")));
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("(FF)V")));
+
+  obj("Foo", 1);
+  // obj("Foo", 2.f); // doesn't compile (good).
+  obj("Foo", "test");
+  obj("Foo", "test1", "test2");
+  obj("Foo", "this_doesnt", "trigger_method_lookup");
+  obj("Baz", 1);
+  obj("Baz", "test3", "test4");
+  obj("Baz", 1234.f, 5678.f);
+}
+
+}  // namespace
+
+#endif  // __clang__
\ No newline at end of file
diff --git a/implementation/legacy/self_test.cc b/implementation/legacy/self_test.cc
new file mode 100644
index 00000000..cd422cff
--- /dev/null
+++ b/implementation/legacy/self_test.cc
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "jni_bind.h"
+#include "jni_test.h"
+
+#if __clang__
+
+namespace {
+
+using ::jni::Class;
+using ::jni::Fake;
+using ::jni::LocalObject;
+using ::jni::Method;
+using ::jni::Params;
+using ::jni::Return;
+using ::jni::Self;
+using ::jni::test::AsGlobal;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::StrEq;
+
+static constexpr Class kClass{"Builder", Method{"build", Return{Self{}}},
+                              Method{"takesBuilder", Return{}, Params{Self{}}}};
+
+TEST_F(JniTest, SelfCanBeUsedAsAReturnValue) {
+  EXPECT_CALL(*env_, FindClass(StrEq("Builder")))
+      .WillOnce(::testing::Return(Fake<jclass>()));
+  EXPECT_CALL(*env_, GetMethodID(AsGlobal(Fake<jclass>()), StrEq("build"),
+                                 StrEq("()LBuilder;")));
+
+  LocalObject<kClass> obj{Fake<jobject>()};
+  obj("build");
+}
+
+TEST_F(JniTest, SelfCanBeUsedAsAReturnValueAndMaintainsRichDecoration) {
+  EXPECT_CALL(*env_, GetMethodID).Times(AnyNumber());
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("build"), StrEq("()LBuilder;")));
+
+  LocalObject<kClass> obj{Fake<jobject>()};
+  obj("build")("build")("build");
+}
+
+TEST_F(JniTest, SelfCanBeUsedAsParam) {
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("takesBuilder"), StrEq("(LBuilder;)V")));
+
+  LocalObject<kClass> obj_1{Fake<jobject>(1)};
+  LocalObject<kClass> obj_2{Fake<jobject>(2)};
+
+  obj_1("takesBuilder", obj_2);
+}
+
+}  // namespace
+
+#endif  // __clang__
\ No newline at end of file
diff --git a/implementation/legacy/static_ref_test.cc b/implementation/legacy/static_ref_test.cc
new file mode 100644
index 00000000..54e02fc6
--- /dev/null
+++ b/implementation/legacy/static_ref_test.cc
@@ -0,0 +1,377 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+#if __clang__
+
+namespace {
+
+using ::jni::AdoptLocal;
+using ::jni::Array;
+using ::jni::Class;
+using ::jni::Fake;
+using ::jni::Field;
+using ::jni::LocalObject;
+using ::jni::LocalString;
+using ::jni::Method;
+using ::jni::Params;
+using ::jni::Rank;
+using ::jni::Self;
+using ::jni::Static;
+using ::jni::StaticRef;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::Return;
+using ::testing::StrEq;
+
+////////////////////////////////////////////////////////////////////////////////
+// Fields.
+////////////////////////////////////////////////////////////////////////////////
+
+static constexpr Class kClass2{"kClass2"};
+
+// clang-format off
+static constexpr Class kClass{
+  "kClass",
+      Static {
+        Field{"booleanField", jboolean{}},
+        Field{"byteField", jbyte{}},
+        Field{"charField", jchar{}},
+        Field{"shortField", jshort{}},
+        Field{"intField", jint{}},
+        Field{"longField", jlong{}},
+        Field{"floatField", jfloat{}},
+        Field{"doubleField", jdouble{}},
+        Field{"stringField", jstring{}},
+        Field{"classField", Class{"kClass2"}},
+        Field{"selfField", Self{}}
+      },
+};
+// clang-format on
+
+TEST_F(JniTest, StaticBooleanField) {
+  EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("booleanField"), StrEq("Z")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetStaticBooleanField(_, Fake<jfieldID>()))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*env_, SetStaticBooleanField(_, Fake<jfieldID>(), true));
+
+  LocalObject<kClass> obj;
+  EXPECT_TRUE(StaticRef<kClass>{}["booleanField"].Get());
+  StaticRef<kClass>{}["booleanField"].Set(true);
+}
+
+TEST_F(JniTest, StaticByteField) {
+  EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("byteField"), StrEq("B")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetStaticByteField(_, Fake<jfieldID>()))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*env_, SetStaticByteField(_, Fake<jfieldID>(), true));
+
+  LocalObject<kClass> obj;
+  EXPECT_TRUE(StaticRef<kClass>{}["byteField"].Get());
+  StaticRef<kClass>{}["byteField"].Set(true);
+}
+
+TEST_F(JniTest, StaticCharField) {
+  EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("charField"), StrEq("C")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetStaticCharField(_, Fake<jfieldID>()))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*env_, SetStaticCharField(_, Fake<jfieldID>(), true));
+
+  LocalObject<kClass> obj;
+  EXPECT_TRUE(StaticRef<kClass>{}["charField"].Get());
+  StaticRef<kClass>{}["charField"].Set(true);
+}
+
+TEST_F(JniTest, StaticShortField) {
+  EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("shortField"), StrEq("S")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetStaticShortField(_, Fake<jfieldID>()))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*env_, SetStaticShortField(_, Fake<jfieldID>(), true));
+
+  LocalObject<kClass> obj;
+  EXPECT_TRUE(StaticRef<kClass>{}["shortField"].Get());
+  StaticRef<kClass>{}["shortField"].Set(true);
+}
+
+TEST_F(JniTest, StaticIntField) {
+  EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("intField"), StrEq("I")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetStaticIntField(_, Fake<jfieldID>()))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*env_, SetStaticIntField(_, Fake<jfieldID>(), true));
+
+  LocalObject<kClass> obj;
+  EXPECT_TRUE(StaticRef<kClass>{}["intField"].Get());
+  StaticRef<kClass>{}["intField"].Set(true);
+}
+
+TEST_F(JniTest, StaticLongField) {
+  EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("longField"), StrEq("J")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetStaticLongField(_, Fake<jfieldID>()))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*env_, SetStaticLongField(_, Fake<jfieldID>(), true));
+
+  LocalObject<kClass> obj;
+  EXPECT_TRUE(StaticRef<kClass>{}["longField"].Get());
+  StaticRef<kClass>{}["longField"].Set(true);
+}
+
+TEST_F(JniTest, StaticFloatField) {
+  EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("floatField"), StrEq("F")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetStaticFloatField(_, Fake<jfieldID>()))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*env_, SetStaticFloatField(_, Fake<jfieldID>(), true));
+
+  LocalObject<kClass> obj;
+  EXPECT_TRUE(StaticRef<kClass>{}["floatField"].Get());
+  StaticRef<kClass>{}["floatField"].Set(true);
+}
+
+TEST_F(JniTest, StaticDoubleField) {
+  EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("doubleField"), StrEq("D")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetStaticDoubleField(_, Fake<jfieldID>()))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*env_, SetStaticDoubleField(_, Fake<jfieldID>(), true));
+
+  LocalObject<kClass> obj;
+  EXPECT_TRUE(StaticRef<kClass>{}["doubleField"].Get());
+  StaticRef<kClass>{}["doubleField"].Set(true);
+}
+
+TEST_F(JniTest, StaticField_ObjectGet) {
+  EXPECT_CALL(*env_,
+              GetStaticFieldID(_, StrEq("classField"), StrEq("LkClass2;")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetStaticObjectField(_, Fake<jfieldID>()))
+      .WillOnce(Return(Fake<jobject>()));
+
+  jni::LocalObject<kClass2> obj = StaticRef<kClass>{}["classField"].Get();
+}
+
+TEST_F(JniTest, StaticField_StringSet) {
+  EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("stringField"),
+                                      StrEq("Ljava/lang/String;")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_,
+              SetStaticObjectField(_, Fake<jfieldID>(), Fake<jstring>()));
+
+  StaticRef<kClass>{}["stringField"].Set(
+      LocalString{AdoptLocal{}, Fake<jstring>()});
+}
+
+TEST_F(JniTest, StaticField_ObjectSet) {
+  EXPECT_CALL(*env_,
+              GetStaticFieldID(_, StrEq("classField"), StrEq("LkClass2;")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_,
+              SetStaticObjectField(_, Fake<jfieldID>(), Fake<jobject>()));
+
+  StaticRef<kClass>{}["classField"].Set(
+      LocalObject<kClass2>{AdoptLocal{}, Fake<jobject>()});
+}
+
+TEST_F(JniTest, StaticField_SelfGet) {
+  EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("selfField"), StrEq("LkClass;")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_, GetStaticObjectField(_, Fake<jfieldID>()))
+      .WillOnce(Return(Fake<jobject>()));
+
+  jni::LocalObject<kClass> obj = StaticRef<kClass>{}["selfField"].Get();
+}
+
+TEST_F(JniTest, StaticField_SelfSet) {
+  EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("selfField"), StrEq("LkClass;")))
+      .WillOnce(Return(Fake<jfieldID>()));
+  EXPECT_CALL(*env_,
+              SetStaticObjectField(_, Fake<jfieldID>(), Fake<jobject>()));
+
+  StaticRef<kClass>{}["selfField"].Set(
+      LocalObject<kClass>{AdoptLocal{}, Fake<jobject>()});
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Static Methods.
+////////////////////////////////////////////////////////////////////////////////
+
+// clang-format off
+static constexpr Class kMethodClass{
+  "kMethodClass",
+      Static {
+        Method{"booleanMethod", ::jni::Return{jboolean{}}},
+        Method{"byteMethod", ::jni::Return{jbyte{}}},
+        Method{"charMethod", ::jni::Return{jchar{}}},
+        Method{"shortMethod", ::jni::Return{jshort{}}},
+        Method{"intMethod", ::jni::Return{jint{}}},
+        Method{"longMethod", ::jni::Return{jlong{}}},
+        Method{"floatMethod", ::jni::Return{jfloat{}}},
+        Method{"doubleMethod", ::jni::Return{jdouble{}}},
+        Method{"stringMethod", ::jni::Return{jstring{}}},
+        Method{"objectMethod", ::jni::Return{Class{"kClass2"}}},
+        Method{"rank1ArrayMethod", ::jni::Return{Array{Class{"kClass2"}}}},
+        Method{"rank2ArrayMethod", ::jni::Return{Array{Class{"kClass2"}, Rank<2>{}}}},
+        Method{"selfMethod", ::jni::Return{Self{}}},
+
+        Method{"simpleFunc", ::jni::Return{int{}}, Params{jfloat{}}},
+        Method{"complexFunc", ::jni::Return{float{}},
+          Params{
+            Array{Class{"kClass2"}, Rank<2>{}}, int{}, float{}, Class{"kClass3"},
+          }
+        }
+      },
+};
+// clang-format on
+
+TEST_F(JniTest, StaticExerciseAllReturns) {
+  EXPECT_CALL(*env_,
+              GetStaticMethodID(_, StrEq("booleanMethod"), StrEq("()Z")));
+  EXPECT_CALL(*env_, CallStaticBooleanMethodV);
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("byteMethod"), StrEq("()B")));
+  EXPECT_CALL(*env_, CallStaticByteMethodV);
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("charMethod"), StrEq("()C")));
+  EXPECT_CALL(*env_, CallStaticCharMethodV);
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("shortMethod"), StrEq("()S")));
+  EXPECT_CALL(*env_, CallStaticShortMethodV);
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("intMethod"), StrEq("()I")));
+  EXPECT_CALL(*env_, CallStaticIntMethodV);
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("longMethod"), StrEq("()J")));
+  EXPECT_CALL(*env_, CallStaticLongMethodV);
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("floatMethod"), StrEq("()F")));
+  EXPECT_CALL(*env_, CallStaticFloatMethodV);
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("doubleMethod"), StrEq("()D")));
+  EXPECT_CALL(*env_, CallStaticDoubleMethodV);
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("stringMethod"),
+                                       StrEq("()Ljava/lang/String;")));
+
+  EXPECT_CALL(
+      *env_, GetStaticMethodID(_, StrEq("objectMethod"), StrEq("()LkClass2;")));
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("rank1ArrayMethod"),
+                                       StrEq("()[LkClass2;")));
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("rank2ArrayMethod"),
+                                       StrEq("()[[LkClass2;")));
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("selfMethod"),
+                                       StrEq("()LkMethodClass;")));
+
+  EXPECT_CALL(*env_, CallStaticObjectMethodV).Times(5);
+
+  StaticRef<kMethodClass>{}("booleanMethod");
+  StaticRef<kMethodClass>{}("byteMethod");
+  StaticRef<kMethodClass>{}("charMethod");
+  StaticRef<kMethodClass>{}("shortMethod");
+  StaticRef<kMethodClass>{}("intMethod");
+  StaticRef<kMethodClass>{}("longMethod");
+  StaticRef<kMethodClass>{}("floatMethod");
+  StaticRef<kMethodClass>{}("doubleMethod");
+  StaticRef<kMethodClass>{}("stringMethod");
+
+  StaticRef<kMethodClass>{}("objectMethod");
+  StaticRef<kMethodClass>{}("rank1ArrayMethod");
+  StaticRef<kMethodClass>{}("rank2ArrayMethod");
+
+  LocalObject<kClass> self_ret = StaticRef<kMethodClass>{}("selfMethod");
+}
+
+// clang-format off
+static constexpr Class kMethodClassSingleParam{
+  "kMethodClassSingleParam",
+      Static {
+        Method{"booleanMethod", ::jni::Return<void>{}, Params<jboolean>{}},
+        Method{"byteMethod", ::jni::Return<void>{}, Params<jbyte>{}},
+        Method{"charMethod", ::jni::Return<void>{}, Params<jchar>{}},
+        Method{"shortMethod", ::jni::Return<void>{}, Params<jshort>{}},
+        Method{"intMethod", ::jni::Return<void>{}, Params<jint>{}},
+        Method{"longMethod", ::jni::Return<void>{}, Params<jlong>{}},
+        Method{"floatMethod", ::jni::Return<void>{}, Params<jfloat>{}},
+        Method{"doubleMethod", ::jni::Return<void>{}, Params<jdouble>{}},
+        Method{"stringMethod", ::jni::Return<void>{}, Params<jstring>{}},
+        Method{"objectMethod", ::jni::Return<void>{}, Params{Class{"kClass2"}}},
+        Method{"rank1ArrayMethod", ::jni::Return<void>{}, Params{Array{Class{"kClass2"}}}},
+        Method{"rank2ArrayMethod", ::jni::Return<void>{}, Params{Array{Class{"kClass2"}, Rank<2>{}}}},
+        Method{"selfMethod", ::jni::Return<void>{}, Params{Self{}}}
+      },
+};
+// clang-format on
+
+TEST_F(JniTest, StaticExerciseAllTypesThroughSingleParam) {
+  EXPECT_CALL(*env_,
+              GetStaticMethodID(_, StrEq("booleanMethod"), StrEq("(Z)V")));
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("byteMethod"), StrEq("(B)V")));
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("charMethod"), StrEq("(C)V")));
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("shortMethod"), StrEq("(S)V")));
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("intMethod"), StrEq("(I)V")));
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("longMethod"), StrEq("(J)V")));
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("floatMethod"), StrEq("(F)V")));
+  EXPECT_CALL(*env_,
+              GetStaticMethodID(_, StrEq("doubleMethod"), StrEq("(D)V")));
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("stringMethod"),
+                                       StrEq("(Ljava/lang/String;)V")));
+
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("objectMethod"),
+                                       StrEq("(LkClass2;)V")));
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("rank1ArrayMethod"),
+                                       StrEq("([LkClass2;)V")));
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("rank2ArrayMethod"),
+                                       StrEq("([[LkClass2;)V")));
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("selfMethod"),
+                                       StrEq("(LkMethodClassSingleParam;)V")));
+
+  EXPECT_CALL(*env_, CallStaticVoidMethodV).Times(13);
+
+  StaticRef<kMethodClassSingleParam>{}("booleanMethod", jboolean{true});
+  StaticRef<kMethodClassSingleParam>{}("byteMethod", jbyte{1});
+  StaticRef<kMethodClassSingleParam>{}("charMethod", jchar{'a'});
+  StaticRef<kMethodClassSingleParam>{}("shortMethod", jshort{1});
+  StaticRef<kMethodClassSingleParam>{}("intMethod", jint{123});
+  StaticRef<kMethodClassSingleParam>{}("longMethod", jlong{456});
+  StaticRef<kMethodClassSingleParam>{}("floatMethod", jfloat{789.f});
+  StaticRef<kMethodClassSingleParam>{}("doubleMethod", jdouble{101.});
+  StaticRef<kMethodClassSingleParam>{}("stringMethod", "test");
+
+  // It would be more complete to exercise all types here.
+  StaticRef<kMethodClassSingleParam>{}("objectMethod", Fake<jobject>());
+  StaticRef<kMethodClassSingleParam>{}("rank1ArrayMethod",
+                                       Fake<jobjectArray>());
+  StaticRef<kMethodClassSingleParam>{}("rank2ArrayMethod",
+                                       Fake<jobjectArray>());
+  StaticRef<kMethodClassSingleParam>{}("selfMethod", Fake<jobject>());
+}
+
+TEST_F(JniTest, StaticExerciseComplexSetOfParams) {
+  // The primary difference for statics are how they handle their returns.
+  // Coverage is already fairly extensive for params.
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("simpleFunc"), StrEq("(F)I")));
+  EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("complexFunc"),
+                                       StrEq("([[LkClass2;IFLkClass3;)F")));
+
+  StaticRef<kMethodClass>{}("simpleFunc", 123.f);
+  StaticRef<kMethodClass>{}("complexFunc", jobjectArray{nullptr}, 123, 456.f,
+                            jobject{nullptr});
+}
+
+}  // namespace
+
+#endif  // __clang__
\ No newline at end of file
diff --git a/implementation/legacy/string_ref_test.cc b/implementation/legacy/string_ref_test.cc
new file mode 100644
index 00000000..23136d92
--- /dev/null
+++ b/implementation/legacy/string_ref_test.cc
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <string_view>
+#include <utility>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "implementation/jni_helper/fake_test_constants.h"
+#include "jni_bind.h"
+#include "jni_test.h"
+
+#if __clang__
+
+namespace {
+
+using ::jni::AdoptGlobal;
+using ::jni::Fake;
+using ::jni::GlobalObject;
+using ::jni::GlobalString;
+using ::jni::kJavaLangString;
+using ::jni::LocalObject;
+using ::jni::LocalString;
+using ::jni::NewRef;
+using ::jni::UtfStringView;
+using ::jni::test::AsNewLocalReference;
+using ::jni::test::JniTest;
+using ::testing::_;
+using ::testing::Return;
+using ::testing::StrEq;
+
+const char* char_ptr = "TestString";
+
+static constexpr jni::Class kClass{
+    "Class",
+    jni::Method{"Foo", jni::Return<jstring>{}, jni::Params{}},
+    jni::Method{"TakesStrParam", jni::Return<void>{}, jni::Params<jstring>{}},
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Local String Tests.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, LocalString_NullPtrT) { LocalString str{nullptr}; }
+
+TEST_F(JniTest, LocalString_IsImplicitlyConvertible) {
+  LocalString str{Fake<jstring>()};
+  EXPECT_EQ(static_cast<jstring>(str), AsNewLocalReference(Fake<jstring>()));
+}
+
+TEST_F(JniTest, LocalString_NullWorks) {
+  EXPECT_CALL(*env_, DeleteLocalRef).Times(0);
+  LocalString str{jstring{nullptr}};
+}
+
+TEST_F(JniTest, LocalString_ConstructsFromObject) {
+  EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobject>())));
+  LocalObject<kJavaLangString> undecorated_object{Fake<jobject>()};
+  LocalString decorated_object{std::move(undecorated_object)};
+}
+
+TEST_F(JniTest, LocalString_CopiesFromObject) {
+  EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake<jobject>())));
+  EXPECT_CALL(*env_, NewLocalRef(Fake<jobject>()));
+
+  LocalString decorated_object{NewRef{}, Fake<jobject>()};
+
+  EXPECT_EQ(jstring{decorated_object}, AsNewLocalReference(Fake<jobject>()));
+}
+
+TEST_F(JniTest, LocalString_CopiesFromJString) {
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jstring>(2)));
+  EXPECT_CALL(*env_, NewLocalRef(Fake<jstring>(1)))
+      .WillOnce(::testing::Return(Fake<jstring>(2)));
+
+  LocalString decorated_object{NewRef{}, Fake<jstring>(1)};
+
+  EXPECT_EQ(jstring{decorated_object}, Fake<jstring>(2));
+}
+
+TEST_F(JniTest, LocalString_ConstructsFromOutputOfMethod) {
+  LocalObject<kClass> obj{};
+  LocalString str{obj("Foo")};
+}
+
+TEST_F(JniTest, LocalString_ConstructsFromByteArray) {
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("<init>"), StrEq("([B)V")));
+  LocalString str{Fake<jbyteArray>()};
+}
+
+TEST_F(JniTest, LocalString_CreatesFromCharPtr) {
+  LocalString str{"TestString"};
+}
+
+TEST_F(JniTest, LocalString_CreatesFromStringView) {
+  EXPECT_CALL(*env_, NewStringUTF(StrEq("TestString")))
+      .WillOnce(Return(Fake<jstring>()));
+
+  // jclass for temp String class reference.
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>()));
+  // The variable str (which is itself an object).
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>()));
+  // TODO(b/143908983): Currently strings leak one local during proxying.
+  // Temporary xref created during construction.
+  // EXPECT_CALL(*env_, DeleteLocalRef(Fake<jstring>()));
+
+  LocalString str{std::string_view{char_ptr}};
+}
+
+TEST_F(JniTest, LocalString_CreatesFromString) {
+  EXPECT_CALL(*env_, NewStringUTF(StrEq("TestString")))
+      .WillOnce(Return(Fake<jstring>()));
+
+  // jclass for temp String class reference.
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jclass>()));
+  // The variable str (which is itself an object).
+  EXPECT_CALL(*env_, DeleteLocalRef(Fake<jobject>()));
+  // TODO(b/143908983): Currently strings leak one local during proxying.
+  // Temporary xref created during construction.
+  // EXPECT_CALL(*env_, DeleteLocalRef(Fake<jstring>()));
+  LocalString str{std::string{"TestString"}};
+}
+
+TEST_F(JniTest, LocalString_CreatesFromCharPtrForGlobals) {
+  GlobalString str{"TestString"};
+}
+
+TEST_F(JniTest, LocalString_PinsAndUnpinsMemoryForLocals) {
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("<init>"), StrEq("(Ljava/lang/String;)V")));
+  EXPECT_CALL(*env_, NewStringUTF(StrEq("TestLocalString")));
+  EXPECT_CALL(*env_, GetStringUTFChars(_, nullptr)).WillOnce(Return(char_ptr));
+  EXPECT_CALL(*env_, ReleaseStringUTFChars(_, char_ptr));
+
+  LocalString str{"TestLocalString"};
+  UtfStringView utf_string_view = str.Pin();
+  EXPECT_EQ(utf_string_view.ToString().data(), char_ptr);
+}
+
+TEST_F(JniTest, LocalString_AllowsLValueLocalString) {
+  LocalObject<kClass> obj{};
+  LocalString local_string{"abcde"};
+  obj("TakesStrParam", local_string);
+}
+
+TEST_F(JniTest, LocalString_AllowsRValueLocalString) {
+  LocalObject<kClass> obj{};
+  obj("TakesStrParam", LocalString{"abcde"});
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Global String Tests.
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(JniTest, GlobalString_NullWorks) {
+  GlobalString str{AdoptGlobal{}, jstring{nullptr}};
+}
+
+TEST_F(JniTest, GlobalString_ConstructsFromObject) {
+  EXPECT_CALL(*env_, DeleteGlobalRef).Times(1);
+  GlobalObject<kJavaLangString> undecorated_object{AdoptGlobal{},
+                                                   Fake<jobject>()};
+  GlobalString decorated_object{std::move(undecorated_object)};
+}
+
+TEST_F(JniTest, GlobalString_GlobalsReleaseWithGlobalMechanism) {
+  EXPECT_CALL(*env_, DeleteGlobalRef);
+  GlobalString str{AdoptGlobal{}, Fake<jstring>()};
+}
+
+TEST_F(JniTest, GlobalString_ConstructsFromOutputOfMethod) {
+  LocalObject<kClass> obj{};
+  GlobalString str{obj("Foo")};
+}
+
+TEST_F(JniTest, GlobalString_ConstructsFromByteArray) {
+  EXPECT_CALL(*env_, GetMethodID(_, StrEq("<init>"), StrEq("([B)V")));
+  GlobalString str{Fake<jbyteArray>()};
+}
+
+TEST_F(JniTest, GlobalString_CreatesFromCharPtr) {
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("<init>"), StrEq("(Ljava/lang/String;)V")));
+  GlobalString str{"TestString"};
+}
+
+TEST_F(JniTest, GlobalString_CreatesFromStringView) {
+  EXPECT_CALL(*env_, NewStringUTF(StrEq("TestString")))
+      .WillOnce(Return(Fake<jstring>()));
+  GlobalString str{std::string_view{char_ptr}};
+}
+
+TEST_F(JniTest, GlobalString_CreatesFromString) {
+  EXPECT_CALL(*env_, NewStringUTF(StrEq("TestString")))
+      .WillOnce(Return(Fake<jstring>()));
+  GlobalString str{std::string{"TestString"}};
+}
+
+TEST_F(JniTest, GlobalString_CreatesFromCharPtrForGlobals) {
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("<init>"), StrEq("(Ljava/lang/String;)V")));
+  GlobalString str{"TestString"};
+}
+
+TEST_F(JniTest, GlobalString_PinsAndUnpinsMemoryForLocals) {
+  EXPECT_CALL(*env_,
+              GetMethodID(_, StrEq("<init>"), StrEq("(Ljava/lang/String;)V")));
+  EXPECT_CALL(*env_, NewStringUTF(StrEq("TestGlobalString")));
+  EXPECT_CALL(*env_, GetStringUTFChars(_, nullptr)).WillOnce(Return(char_ptr));
+  EXPECT_CALL(*env_, ReleaseStringUTFChars(_, char_ptr));
+
+  GlobalString str{"TestGlobalString"};
+  UtfStringView utf_string_view = str.Pin();
+  EXPECT_EQ(utf_string_view.ToString().data(), char_ptr);
+}
+
+TEST_F(JniTest, GlobalString_AllowsLValueGlobalString) {
+  LocalObject<kClass> obj{};
+  GlobalString global_string{"abcde"};
+  obj("TakesStrParam", global_string);
+}
+
+TEST_F(JniTest, GlobalString_AllowsRValueGlobalString) {
+  LocalObject<kClass> obj{};
+  obj("TakesStrParam", GlobalString{"abcde"});
+}
+
+}  // namespace
+
+#endif  // __clang__
\ No newline at end of file
diff --git a/implementation/proxy_test.cc b/implementation/proxy_test.cc
index f82ed8b6..50652843 100644
--- a/implementation/proxy_test.cc
+++ b/implementation/proxy_test.cc
@@ -23,6 +23,7 @@
 #include <gtest/gtest.h>
 #include "jni_bind.h"
 #include "jni_test.h"
+#include "metaprogramming/type_to_type_map.h"
 
 using ::jni::AsDecl_t;
 using ::jni::Class;
diff --git a/implementation/string_ref_test.cc b/implementation/string_ref_test.cc
index 722def8f..08156c61 100644
--- a/implementation/string_ref_test.cc
+++ b/implementation/string_ref_test.cc
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
+#include <string>
+#include <string_view>
+#include <utility>
+
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include "implementation/jni_helper/fake_test_constants.h"
 #include "jni_bind.h"