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

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


The following commit(s) were added to refs/heads/master by this push:
     new c56fb2464 KUDU-1261 [Java] Add read support for Array Type
c56fb2464 is described below

commit c56fb2464ecad91a58d4589e1075de20e0f75c71
Author: Abhishek Chennaka <[email protected]>
AuthorDate: Fri Oct 3 10:46:00 2025 -0700

    KUDU-1261 [Java] Add read support for Array Type
    
    Added support utilities for array cell handling in Kudu client
    - Introduced ArrayCellViewHelper for consistent array data access/printing
    - Extended RowResult and RowwiseRowResult with array-aware getters
    - Added unit tests in TestKuduClient and TestRowResult for empty/null arrays
    - Updated ClientTestUtil helpers to support array column creation
    
    Change-Id: Ie26750cf540b0097c77e5454c1c1d20b3a194c52
    Reviewed-on: http://gerrit.cloudera.org:8080/23483
    Reviewed-by: Alexey Serbin <[email protected]>
    Tested-by: Abhishek Chennaka <[email protected]>
---
 java/config/spotbugs/excludeFilter.xml             |  61 +++-
 .../java/org/apache/kudu/client/ArrayCellView.java | 225 +++++++++---
 .../apache/kudu/client/ArrayCellViewHelper.java    | 358 +++++++++++++++++++
 .../java/org/apache/kudu/client/RowResult.java     | 245 ++++++++++++-
 .../org/apache/kudu/client/RowwiseRowResult.java   |  49 ++-
 .../org/apache/kudu/client/TestKuduClient.java     | 396 +++++++++++++++++++++
 .../org/apache/kudu/client/TestPartialRow.java     |   2 -
 .../java/org/apache/kudu/client/TestRowResult.java | 139 ++++++++
 .../java/org/apache/kudu/test/ClientTestUtil.java  |  32 ++
 9 files changed, 1438 insertions(+), 69 deletions(-)

diff --git a/java/config/spotbugs/excludeFilter.xml 
b/java/config/spotbugs/excludeFilter.xml
index 453965d55..005fbea26 100644
--- a/java/config/spotbugs/excludeFilter.xml
+++ b/java/config/spotbugs/excludeFilter.xml
@@ -174,8 +174,8 @@
     </Match>
 
     <!-- kudu-client exclusions -->
+    <!-- For invalid elements we return null. -->
     <Match>
-        <!-- For invalid elements we return null. -->
         <Class name="org.apache.kudu.client.ArrayCellView"/>
         <Method name="getBinary" />
         <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS" />
@@ -185,6 +185,65 @@
         <Method name="normalizeValidity" />
         <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS" />
     </Match>
+    <Match>
+        <Class name="org.apache.kudu.client.RowResult"/>
+        <Method name="getArrayBytes" />
+        <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS" />
+    </Match>
+    <!-- FlatBuffers union accessors always return the correct subclass;
+    safe cast verified by ensureTag()
+    -->
+    <Match>
+        <Class name="org.apache.kudu.client.ArrayCellView"/>
+        <Method name="asBinaryFB" />
+        <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS" />
+    </Match>
+
+    <Match>
+        <Class name="org.apache.kudu.client.ArrayCellView"/>
+        <Method name="asDoubleFB" />
+        <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+    </Match>
+    <Match>
+        <Class name="org.apache.kudu.client.ArrayCellView"/>
+        <Method name="asBinaryFB" />
+        <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+    </Match>
+    <Match>
+        <Class name="org.apache.kudu.client.ArrayCellView"/>
+        <Method name="asFloatFB" />
+        <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+    </Match>
+    <Match>
+        <Class name="org.apache.kudu.client.ArrayCellView"/>
+        <Method name="asInt16FB" />
+        <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+    </Match>
+    <Match>
+        <Class name="org.apache.kudu.client.ArrayCellView"/>
+        <Method name="asInt32FB" />
+        <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+    </Match>
+    <Match>
+        <Class name="org.apache.kudu.client.ArrayCellView"/>
+        <Method name="asInt64FB" />
+        <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+    </Match>
+    <Match>
+        <Class name="org.apache.kudu.client.ArrayCellView"/>
+        <Method name="asInt8FB" />
+        <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+    </Match>
+    <Match>
+        <Class name="org.apache.kudu.client.ArrayCellView"/>
+        <Method name="asStringFB" />
+        <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+    </Match>
+    <Match>
+        <Class name="org.apache.kudu.client.ArrayCellView"/>
+        <Method name="asUInt8FB" />
+        <Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
+    </Match>
     <Match>
         <!-- Reference equality is intended here. -->
         <Class name="org.apache.kudu.client.AsyncKuduClient"/>
diff --git 
a/java/kudu-client/src/main/java/org/apache/kudu/client/ArrayCellView.java 
b/java/kudu-client/src/main/java/org/apache/kudu/client/ArrayCellView.java
index d16798efb..62a0266e6 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/ArrayCellView.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/ArrayCellView.java
@@ -22,6 +22,8 @@ import java.nio.ByteOrder;
 import java.util.Arrays;
 import java.util.function.IntFunction;
 
+import org.apache.kudu.ColumnSchema;
+import org.apache.kudu.Type;
 import org.apache.kudu.serdes.BinaryArray;
 import org.apache.kudu.serdes.Content;
 import org.apache.kudu.serdes.DoubleArray;
@@ -40,7 +42,7 @@ import org.apache.kudu.serdes.UInt8Array;
 /**
  * Lightweight view over a FlatBuffers Content blob representing a single 
array cell.
  */
-public final class ArrayCellView {
+class ArrayCellView {
 
   private final byte[] rawBytes;
   private final Content content;
@@ -48,7 +50,14 @@ public final class ArrayCellView {
 
   private static final String NULL_ELEMENT_MSG = "Element %d is NULL";
 
-  public ArrayCellView(byte[] buf) {
+  /**
+   * Construct an {@code ArrayCellView} from a serialized FlatBuffer blob.
+   * The byte array is not copied; callers must ensure it remains valid.
+   *
+   * @param buf the FlatBuffer bytes for a {@code Content} object
+   */
+
+  ArrayCellView(byte[] buf) {
     this.rawBytes = buf;
     ByteBuffer bb = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN);
     this.content = Content.getRootAsContent(bb);
@@ -56,12 +65,13 @@ public final class ArrayCellView {
   }
 
   /** Return the underlying FlatBuffer bytes exactly as passed in. */
-  public byte[] toBytes() {
+
+  byte[] toBytes() {
     return rawBytes;
   }
 
   /** Number of logical elements (driven by the data vector). */
-  public int length() {
+  int length() {
     switch (typeTag) {
       case ScalarArray.Int8Array: {
         Int8Array arr = new Int8Array();
@@ -130,7 +140,7 @@ public final class ArrayCellView {
   }
 
   /** Returns true iff element i is valid (non-null). */
-  public boolean isValid(int i) {
+  boolean isValid(int i) {
     int n = length();
     if (i < 0 || i >= n) {
       throw new IndexOutOfBoundsException(
@@ -151,12 +161,38 @@ public final class ArrayCellView {
     return content.validity(i);
   }
 
+  /**
+   * Returns a boolean array of element validity flags.
+   *
+   * @param n expected number of elements in the values vector
+   * @throws IllegalStateException if validity length and value length mismatch
+   */
+
+  boolean[] validityOrAllTrue(int n) {
+    int len = content.validityLength();
+    if (len == 0) {
+      boolean[] allTrue = new boolean[n];
+      java.util.Arrays.fill(allTrue, true);
+      return allTrue;
+    }
+    if (len != n) {
+      throw new IllegalStateException(
+          String.format("Validity length %d does not match values length %d", 
len, n));
+    }
+    boolean[] v = new boolean[n];
+    for (int i = 0; i < n; i++) {
+      v[i] = content.validity(i);
+    }
+    return v;
+  }
+
   // ----------------------------------------------------------------------
   // Types single element accessors
   // ----------------------------------------------------------------------
 
   /** BOOL is encoded as UInt8 (0/1). */
-  public boolean getBoolean(int i) {
+
+  boolean getBoolean(int i) {
     ensureTag(ScalarArray.UInt8Array);
     if (!isValid(i)) {
       throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
@@ -166,7 +202,8 @@ public final class ArrayCellView {
     return arr.values(i) != 0;
   }
 
-  public byte getInt8(int i) {
+
+  byte getInt8(int i) {
     ensureTag(ScalarArray.Int8Array);
     if (!isValid(i)) {
       throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
@@ -176,7 +213,8 @@ public final class ArrayCellView {
     return arr.values(i);
   }
 
-  public short getInt16(int i) {
+
+  short getInt16(int i) {
     ensureTag(ScalarArray.Int16Array);
     if (!isValid(i)) {
       throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
@@ -187,7 +225,8 @@ public final class ArrayCellView {
   }
 
   /** INT32 (also used by DATE, DECIMAL32 on the wire). */
-  public int getInt32(int i) {
+
+  int getInt32(int i) {
     ensureTag(ScalarArray.Int32Array);
     if (!isValid(i)) {
       throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
@@ -198,7 +237,8 @@ public final class ArrayCellView {
   }
 
   /** INT64 (also used by DECIMAL64, UNIXTIME_MICROS on the wire). */
-  public long getInt64(int i) {
+
+  long getInt64(int i) {
     ensureTag(ScalarArray.Int64Array);
     if (!isValid(i)) {
       throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
@@ -208,7 +248,8 @@ public final class ArrayCellView {
     return arr.values(i);
   }
 
-  public float getFloat(int i) {
+
+  float getFloat(int i) {
     ensureTag(ScalarArray.FloatArray);
     if (!isValid(i)) {
       throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
@@ -218,7 +259,8 @@ public final class ArrayCellView {
     return arr.values(i);
   }
 
-  public double getDouble(int i) {
+
+  double getDouble(int i) {
     ensureTag(ScalarArray.DoubleArray);
     if (!isValid(i)) {
       throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
@@ -228,27 +270,28 @@ public final class ArrayCellView {
     return arr.values(i);
   }
 
-  public String getString(int i) {
+
+  String getString(int i) {
     ensureTag(ScalarArray.StringArray);
     if (!isValid(i)) {
-      return null;
+      throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
     }
     StringArray arr = new StringArray();
     content.data(arr);
     return arr.values(i);
   }
 
-  public byte[] getBinary(int i) {
+
+  byte[] getBinary(int i) {
     ensureTag(ScalarArray.BinaryArray);
     if (!isValid(i)) {
-      return null;
+      throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
     }
     BinaryArray arr = new BinaryArray();
     content.data(arr);
     UInt8Array elem = arr.values(new UInt8Array(), i);
     if (elem == null) {
-      throw new IllegalStateException(
-          String.format("Corrupt BinaryArray: missing element at index %d", 
i));
+      throw new IllegalStateException(String.format(NULL_ELEMENT_MSG, i));
     }
     ByteBuffer bb = elem.valuesAsByteBuffer();
     byte[] out = new byte[bb.remaining()];
@@ -257,33 +300,12 @@ public final class ArrayCellView {
   }
 
 
-  // ----------------------------------------------------------------------
-  // Bulk conversion for callers that want plain Java arrays
-  // ----------------------------------------------------------------------
-
-  public Object toJavaArray() {
-    switch (typeTag) {
-      case ScalarArray.Int32Array:   return 
Array1dSerdes.parseInt32(rawBytes).getValues();
-      case ScalarArray.Int64Array:   return 
Array1dSerdes.parseInt64(rawBytes).getValues();
-      case ScalarArray.Int16Array:   return 
Array1dSerdes.parseInt16(rawBytes).getValues();
-      case ScalarArray.Int8Array:    return 
Array1dSerdes.parseInt8(rawBytes).getValues();
-      case ScalarArray.UInt8Array:   return 
Array1dSerdes.parseUInt8(rawBytes).getValues();
-      case ScalarArray.UInt16Array:  return 
Array1dSerdes.parseUInt16(rawBytes).getValues();
-      case ScalarArray.UInt32Array:  return 
Array1dSerdes.parseUInt32(rawBytes).getValues();
-      case ScalarArray.UInt64Array:  return 
Array1dSerdes.parseUInt64(rawBytes).getValues();
-      case ScalarArray.FloatArray:   return 
Array1dSerdes.parseFloat(rawBytes).getValues();
-      case ScalarArray.DoubleArray:  return 
Array1dSerdes.parseDouble(rawBytes).getValues();
-      case ScalarArray.StringArray:  return 
Array1dSerdes.parseString(rawBytes).getValues();
-      case ScalarArray.BinaryArray:  return 
Array1dSerdes.parseBinary(rawBytes).getValues();
-      default:
-        throw new UnsupportedOperationException("Unsupported array type tag: " 
+ typeTag);
-    }
-  }
-
-  // ----------------------------------------------------------------------
-  // Pretty print with NULLs preserved
-  // ----------------------------------------------------------------------
-
+  /**
+   * Returns a human-readable string representation of the array contents,
+   * preserving {@code NULL} markers for invalid elements.
+   *
+   * <p>This is intended for debugging and should not be parsed.</p>
+   */
   @Override
   public String toString() {
     final int n = length();
@@ -383,6 +405,12 @@ public final class ArrayCellView {
     return sb.toString();
   }
 
+  /**
+   * Verifies that this array's type tag matches the expected
+   * physical array type.
+   *
+   * @throws IllegalStateException if the tag does not match
+   */
   private void ensureTag(byte expectedTag) {
     if (typeTag != expectedTag) {
       String expectedName = ScalarArray.name(expectedTag);
@@ -394,6 +422,10 @@ public final class ArrayCellView {
     }
   }
 
+  /**
+   * Appends formatted element values to the provided {@link StringBuilder},
+   * substituting {@code NULL} for invalid entries.
+   */
   private void appendArrayValuesToString(
       StringBuilder sb, int n,
       IntFunction<Object> valueFn) {
@@ -409,4 +441,107 @@ public final class ArrayCellView {
     }
   }
 
+  /**
+   * Converts this array cell into a boxed Java array using the column schema
+   * to resolve the element's logical type and column attributes.
+   *
+   * <p>This variant interprets the element type (for example,
+   * {@code DATE}, {@code TIMESTAMP}, or {@code DECIMAL}) using the
+   * provided {@link ColumnSchema}, and applies precision and scale
+   * information when relevant.</p>
+   *
+   * <p>Internally delegates to
+   * {@link ArrayCellViewHelper#toJavaArray(ArrayCellView, ColumnSchema)}.</p>
+   *
+   * @param columnSchema the schema describing this array column, including
+   *                     logical type, precision, and scale
+   * @return a boxed Java array corresponding to the logical element type
+   */
+
+  Object toJavaArray(ColumnSchema columnSchema) {
+    return ArrayCellViewHelper.toJavaArray(this, columnSchema);
+  }
+
+  /**
+   * Converts this array cell into a boxed Java array without schema context.
+   *
+   * <p>This variant performs a physical-to-Java mapping based only on
+   * the array's storage type and does not apply any logical-type
+   * conversions or precision/scale metadata.</p>
+   *
+   * <p>For {@code DECIMAL} arrays, prefer
+   * {@link #toJavaArray(ColumnSchema)} to ensure correct precision and
+   * scale are applied.</p>
+   *
+   * @return a boxed Java array corresponding to the underlying storage type
+   */
+
+  Object toJavaArray() {
+    return ArrayCellViewHelper.toJavaArray(this, null);
+  }
+
+
+  byte typeTag() {
+    return typeTag;
+  }
+
+  /**
+   * Returns the underlying FlatBuffer view of this array's physical values
+   * (for internal use or advanced diagnostics). Validates that the
+   * {@code typeTag} matches the expected array type.
+   */
+
+  Int8Array asInt8FB() {
+    ensureTag(ScalarArray.Int8Array);
+    return (Int8Array) content.data(new Int8Array());
+  }
+
+
+  Int16Array asInt16FB() {
+    ensureTag(ScalarArray.Int16Array);
+    return (Int16Array) content.data(new Int16Array());
+  }
+
+
+  Int32Array asInt32FB() {
+    ensureTag(ScalarArray.Int32Array);
+    return (Int32Array) content.data(new Int32Array());
+  }
+
+
+  Int64Array asInt64FB() {
+    ensureTag(ScalarArray.Int64Array);
+    return (Int64Array) content.data(new Int64Array());
+  }
+
+
+  FloatArray asFloatFB() {
+    ensureTag(ScalarArray.FloatArray);
+    return (FloatArray) content.data(new FloatArray());
+  }
+
+
+  DoubleArray asDoubleFB() {
+    ensureTag(ScalarArray.DoubleArray);
+    return (DoubleArray) content.data(new DoubleArray());
+  }
+
+
+  UInt8Array asUInt8FB() {
+    ensureTag(ScalarArray.UInt8Array);
+    return (UInt8Array) content.data(new UInt8Array());
+  }
+
+
+  StringArray asStringFB() {
+    ensureTag(ScalarArray.StringArray);
+    return (StringArray) content.data(new StringArray());
+  }
+
+
+  BinaryArray asBinaryFB() {
+    ensureTag(ScalarArray.BinaryArray);
+    return (BinaryArray) content.data(new BinaryArray());
+  }
+
 }
\ No newline at end of file
diff --git 
a/java/kudu-client/src/main/java/org/apache/kudu/client/ArrayCellViewHelper.java
 
b/java/kudu-client/src/main/java/org/apache/kudu/client/ArrayCellViewHelper.java
new file mode 100644
index 000000000..b753cf9c2
--- /dev/null
+++ 
b/java/kudu-client/src/main/java/org/apache/kudu/client/ArrayCellViewHelper.java
@@ -0,0 +1,358 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.kudu.client;
+
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.LongBuffer;
+import java.nio.ShortBuffer;
+import java.sql.Date;
+import java.sql.Timestamp;
+import java.util.Objects;
+
+import org.apache.kudu.ColumnSchema;
+import org.apache.kudu.Type;
+import org.apache.kudu.serdes.BinaryArray;
+import org.apache.kudu.serdes.DoubleArray;
+import org.apache.kudu.serdes.FloatArray;
+import org.apache.kudu.serdes.Int16Array;
+import org.apache.kudu.serdes.Int32Array;
+import org.apache.kudu.serdes.Int64Array;
+import org.apache.kudu.serdes.Int8Array;
+import org.apache.kudu.serdes.ScalarArray;
+import org.apache.kudu.serdes.StringArray;
+import org.apache.kudu.serdes.UInt8Array;
+import org.apache.kudu.util.DateUtil;
+import org.apache.kudu.util.TimestampUtil;
+
+
+class ArrayCellViewHelper {
+
+  private ArrayCellViewHelper() {
+  }
+
+  // ----------------------------------------------------------------------
+  // Common helpers
+  // ----------------------------------------------------------------------
+
+  private static boolean[] validity(ArrayCellView view, int n) {
+    return view.validityOrAllTrue(n);
+  }
+
+  // ----------------------------------------------------------------------
+  // Boxed numeric arrays with null (validity) handling
+  // ----------------------------------------------------------------------
+
+  // Decodes a BOOL array (physically encoded as a UInt8Array) from its 
FlatBuffer view.
+  // Each element is stored as a single byte (0 or 1), not bit-packed.
+  // Using valuesAsByteBuffer() is significantly faster than calling 
arr.values(i) per element,
+  // but FlatBuffers may return a ByteBuffer whose position is not guaranteed 
to start at 0.
+  // To ensure correctness, compute the base offset and perform absolute reads 
(base + i),
+  // which avoids position drift and unnecessary buffer slicing while 
remaining allocation-free.
+  static Boolean[] toBoolArray(ArrayCellView view) {
+    UInt8Array arr = view.asUInt8FB();
+    int n = arr.valuesLength();
+    boolean[] v = validity(view, n);
+    ByteBuffer bb = arr.valuesAsByteBuffer();
+    bb.order(ByteOrder.LITTLE_ENDIAN);
+    int base = bb.position();
+    Boolean[] out = new Boolean[n];
+    for (int i = 0; i < n; i++) {
+      if (!v[i]) {
+        out[i] = null;
+        continue;
+      }
+      out[i] = (bb.get(base + i) & 0xFF) != 0;
+    }
+    return out;
+  }
+
+  // Decodes an INT8 array (physically represented as an Int8Array) from its 
FlatBuffer view.
+  // Each element is stored as a signed 8-bit value in little-endian order.
+  // Similar to toBoolArray(), this implementation accesses the underlying 
ByteBuffer directly
+  // to avoid the overhead of calling arr.values(i) for every element.
+  // Because FlatBuffers may return a ByteBuffer whose position is not 
guaranteed to be 0,
+  // the code computes the base offset and uses absolute reads (base + i) to 
prevent
+  // position drift and unnecessary slicing while remaining allocation-free.
+  // Null elements are filled with Java nulls according to the validity bitmap.
+  static Byte[] toInt8Array(ArrayCellView view) {
+    Int8Array arr = view.asInt8FB();
+    int n = arr.valuesLength();
+    boolean[] v = validity(view, n);
+    ByteBuffer bb = arr.valuesAsByteBuffer();
+    bb.order(ByteOrder.LITTLE_ENDIAN);
+    int base = bb.position();
+    Byte[] out = new Byte[n];
+    for (int i = 0; i < n; i++) {
+      out[i] = v[i] ? bb.get(base + i) : null;
+    }
+    return out;
+  }
+
+  static Short[] toInt16Array(ArrayCellView view) {
+    Int16Array arr = view.asInt16FB();
+    int n = arr.valuesLength();
+    boolean[] v = validity(view, n);
+    ShortBuffer sb = 
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
+    short[] tmp = new short[n];
+    sb.get(tmp);
+    Short[] out = new Short[n];
+    for (int i = 0; i < n; i++) {
+      out[i] = v[i] ? tmp[i] : null;
+    }
+    return out;
+  }
+
+  static Integer[] toInt32Array(ArrayCellView view) {
+    Int32Array arr = view.asInt32FB();
+    int n = arr.valuesLength();
+    boolean[] v = validity(view, n);
+    IntBuffer ib = 
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
+    int[] tmp = new int[n];
+    ib.get(tmp);
+    Integer[] out = new Integer[n];
+    for (int i = 0; i < n; i++) {
+      out[i] = v[i] ? tmp[i] : null;
+    }
+    return out;
+  }
+
+  static Long[] toInt64Array(ArrayCellView view) {
+    Int64Array arr = view.asInt64FB();
+    int n = arr.valuesLength();
+    boolean[] v = validity(view, n);
+    LongBuffer lb = 
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asLongBuffer();
+    long[] tmp = new long[n];
+    lb.get(tmp);
+    Long[] out = new Long[n];
+    for (int i = 0; i < n; i++) {
+      out[i] = v[i] ? tmp[i] : null;
+    }
+    return out;
+  }
+
+  static Float[] toFloatArray(ArrayCellView view) {
+    FloatArray arr = view.asFloatFB();
+    int n = arr.valuesLength();
+    boolean[] v = validity(view, n);
+    FloatBuffer fb = 
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer();
+    float[] tmp = new float[n];
+    fb.get(tmp);
+    Float[] out = new Float[n];
+    for (int i = 0; i < n; i++) {
+      out[i] = v[i] ? tmp[i] : null;
+    }
+    return out;
+  }
+
+  static Double[] toDoubleArray(ArrayCellView view) {
+    DoubleArray arr = view.asDoubleFB();
+    int n = arr.valuesLength();
+    boolean[] v = validity(view, n);
+    DoubleBuffer db = 
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asDoubleBuffer();
+    double[] tmp = new double[n];
+    db.get(tmp);
+    Double[] out = new Double[n];
+    for (int i = 0; i < n; i++) {
+      out[i] = v[i] ? tmp[i] : null;
+    }
+    return out;
+  }
+
+  // ----------------------------------------------------------------------
+  // String-like types
+  // ----------------------------------------------------------------------
+
+  static String[] toStringArray(ArrayCellView view) {
+    StringArray arr = view.asStringFB();
+    int n = arr.valuesLength();
+    boolean[] v = validity(view, n);
+    String[] out = new String[n];
+    for (int i = 0; i < n; i++) {
+      out[i] = v[i] ? arr.values(i) : null;
+    }
+    return out;
+  }
+
+  static String[] toVarcharArray(ArrayCellView view) {
+    // identical to String arrays, truncation handled on write side
+    return toStringArray(view);
+  }
+
+  static byte[][] toBinaryArray(ArrayCellView view) {
+    BinaryArray arr = view.asBinaryFB();
+    int n = arr.valuesLength();
+    boolean[] v = validity(view, n);
+    byte[][] out = new byte[n][];
+    UInt8Array elem = new UInt8Array();
+    for (int i = 0; i < n; i++) {
+      if (!v[i]) {
+        out[i] = null;
+        continue;
+      }
+      ByteBuffer bb = Objects.requireNonNull(arr.values(elem, 
i)).valuesAsByteBuffer();
+      byte[] b = new byte[bb.remaining()];
+      bb.get(b);
+      out[i] = b;
+    }
+    return out;
+  }
+
+  // ----------------------------------------------------------------------
+  // Date and timestamp
+  // ----------------------------------------------------------------------
+
+  static Date[] toDateArray(ArrayCellView view) {
+    Int32Array arr = view.asInt32FB();
+    int n = arr.valuesLength();
+    boolean[] v = validity(view, n);
+    IntBuffer ib = 
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
+    int[] tmp = new int[n];
+    ib.get(tmp);
+    Date[] out = new Date[n];
+    for (int i = 0; i < n; i++) {
+      out[i] = v[i] ? DateUtil.epochDaysToSqlDate(tmp[i]) : null;
+    }
+    return out;
+  }
+
+  static Timestamp[] toTimestampArray(ArrayCellView view) {
+    Int64Array arr = view.asInt64FB();
+    int n = arr.valuesLength();
+    boolean[] v = validity(view, n);
+    LongBuffer lb = 
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asLongBuffer();
+    long[] tmp = new long[n];
+    lb.get(tmp);
+    Timestamp[] out = new Timestamp[n];
+    for (int i = 0; i < n; i++) {
+      out[i] = v[i] ? TimestampUtil.microsToTimestamp(tmp[i]) : null;
+    }
+    return out;
+  }
+
+  // ----------------------------------------------------------------------
+  // Decimal
+  // ----------------------------------------------------------------------
+
+  static BigDecimal[] toDecimalArray(ArrayCellView view, int precision, int 
scale) {
+    boolean[] v;
+    BigDecimal[] out;
+    if (precision <= 9) {
+      Int32Array arr = view.asInt32FB();
+      int n = arr.valuesLength();
+      v = validity(view, n);
+      IntBuffer ib = 
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
+      int[] tmp = new int[n];
+      ib.get(tmp);
+      out = new BigDecimal[n];
+      for (int i = 0; i < n; i++) {
+        out[i] = v[i] ? BigDecimal.valueOf(tmp[i], scale) : null;
+      }
+    } else if (precision <= 18) {
+      Int64Array arr = view.asInt64FB();
+      int n = arr.valuesLength();
+      v = validity(view, n);
+      LongBuffer lb = 
arr.valuesAsByteBuffer().order(ByteOrder.LITTLE_ENDIAN).asLongBuffer();
+      long[] tmp = new long[n];
+      lb.get(tmp);
+      out = new BigDecimal[n];
+      for (int i = 0; i < n; i++) {
+        out[i] = v[i] ? BigDecimal.valueOf(tmp[i], scale) : null;
+      }
+    } else {
+      throw new IllegalStateException("DECIMAL128 arrays not yet supported");
+    }
+    return out;
+  }
+
+  // ----------------------------------------------------------------------
+  // Generic dispatcher
+  // ----------------------------------------------------------------------
+
+  static Object toJavaArray(ArrayCellView view,
+                            ColumnSchema col) {
+
+    int precision = 0;
+    int scale = 0;
+    if (col.getTypeAttributes() != null) {
+      precision = col.getTypeAttributes().getPrecision();
+      scale = col.getTypeAttributes().getScale();
+    }
+    Type logicalType = 
col.getNestedTypeDescriptor().getArrayDescriptor().getElemType();
+
+    switch (view.typeTag()) {
+      case ScalarArray.Int8Array:
+        return toInt8Array(view);
+
+      case ScalarArray.UInt8Array:
+        // BOOL and UINT8 share the same physical encoding.
+        if (logicalType == org.apache.kudu.Type.BOOL) {
+          return toBoolArray(view);
+        }
+        return toInt8Array(view);
+
+      case ScalarArray.Int16Array:
+        return toInt16Array(view);
+
+      case ScalarArray.Int32Array: {
+        // DECIMAL32, DATE, or plain INT32
+        if (precision > 0) {
+          return toDecimalArray(view, precision, scale);
+        }
+        if (logicalType == org.apache.kudu.Type.DATE) {
+          return toDateArray(view);
+        }
+        return toInt32Array(view);
+      }
+
+      case ScalarArray.Int64Array: {
+        // DECIMAL64, UNIXTIME_MICROS, or plain INT64
+        if (precision > 0) {
+          return toDecimalArray(view, precision, scale);
+        }
+        if (logicalType == org.apache.kudu.Type.UNIXTIME_MICROS) {
+          return toTimestampArray(view);
+        }
+        return toInt64Array(view);
+      }
+
+      case ScalarArray.FloatArray:
+        return toFloatArray(view);
+
+      case ScalarArray.DoubleArray:
+        return toDoubleArray(view);
+
+      case ScalarArray.StringArray:
+        // STRING and VARCHAR share the same encoding
+        return toStringArray(view);
+
+      case ScalarArray.BinaryArray:
+        return toBinaryArray(view);
+
+      default:
+        throw new IllegalStateException(String.format(
+            "Unsupported array type tag %d (%s)",
+            view.typeTag(), ScalarArray.name(view.typeTag())));
+    }
+  }
+
+}
\ No newline at end of file
diff --git 
a/java/kudu-client/src/main/java/org/apache/kudu/client/RowResult.java 
b/java/kudu-client/src/main/java/org/apache/kudu/client/RowResult.java
index b3e81cfc8..8e5c036b4 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/RowResult.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/RowResult.java
@@ -19,10 +19,13 @@ package org.apache.kudu.client;
 
 import java.math.BigDecimal;
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.sql.Date;
 import java.sql.Timestamp;
 import java.util.Arrays;
+import javax.annotation.Nullable;
 
+import com.google.common.base.Preconditions;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.apache.yetus.audience.InterfaceStability;
 
@@ -458,7 +461,8 @@ public abstract class RowResult {
    * This method is useful when you don't care about autoboxing
    * and your existing type handling logic is based on Java types.
    *
-   * The Object type is based on the column's {@link Type}:
+   * The returned Object type depends on the column's definition:
+   *  Array columns -> {@link ArrayCellView}
    *  Type.BOOL -> java.lang.Boolean
    *  Type.INT8 -> java.lang.Byte
    *  Type.INT16 -> java.lang.Short
@@ -476,13 +480,20 @@ public abstract class RowResult {
    * @param columnIndex Column index in the schema
    * @return the column's value as an Object, null if the value is null
    * @throws IndexOutOfBoundsException if the column doesn't exist
+   * @throws UnsupportedOperationException if the column type is unsupported
    */
   public final Object getObject(int columnIndex) {
     checkValidColumn(columnIndex);
     if (isNull(columnIndex)) {
       return null;
     }
-    Type type = schema.getColumnByIndex(columnIndex).getType();
+    ColumnSchema col = schema.getColumnByIndex(columnIndex);
+
+    // Special-case: array columns -> return ArrayCellView
+    if (col.isArray()) {
+      return getArrayData(columnIndex);
+    }
+    Type type = col.getType();
     switch (type) {
       case BOOL: return getBoolean(columnIndex);
       case INT8: return getByte(columnIndex);
@@ -595,16 +606,96 @@ public abstract class RowResult {
       if (i != 0) {
         buf.append(", ");
       }
-      Type type = col.getType();
-      buf.append(type.name());
+      // Show the element type for arrays; otherwise the scalar column type.
+      final Type shownType = col.isArray() ?
+          col.getNestedTypeDescriptor().getArrayDescriptor().getElemType()
+          : col.getType();
+
+      buf.append(shownType.name());
+      if (col.isArray()) {
+        buf.append("[]");
+      }
       buf.append(" ").append(col.getName());
       if (col.getTypeAttributes() != null) {
-        buf.append(col.getTypeAttributes().toStringForType(type));
+        buf.append(col.getTypeAttributes().toStringForType(shownType));
       }
       buf.append("=");
       if (isNull(i)) {
         buf.append("NULL");
       } else {
+        if (col.isArray()) {
+          ArrayCellView arr = getArray(i);
+          Preconditions.checkNotNull(arr, "array is null");
+          switch 
(col.getNestedTypeDescriptor().getArrayDescriptor().getElemType()) {
+            case INT8:
+              appendArrayValues(buf, arr, arr::getInt8);
+              break;
+            case INT16:
+              appendArrayValues(buf, arr, arr::getInt16);
+              break;
+            case INT32:
+              appendArrayValues(buf, arr, arr::getInt32);
+              break;
+            case INT64:
+              appendArrayValues(buf, arr, arr::getInt64);
+              break;
+            case FLOAT:
+              appendArrayValues(buf, arr, arr::getFloat);
+              break;
+            case DOUBLE:
+              appendArrayValues(buf, arr, arr::getDouble);
+              break;
+            case BOOL:
+              appendArrayValues(buf, arr, arr::getBoolean);
+              break;
+            case STRING:
+            case VARCHAR:
+              
buf.append(Arrays.toString(ArrayCellViewHelper.toStringArray(arr)));
+              break;
+            case BINARY:
+              byte[][] binaries = ArrayCellViewHelper.toBinaryArray(arr);
+              buf.append("[");
+              for (int j = 0; j < binaries.length; j++) {
+                if (j > 0) {
+                  buf.append(", ");
+                }
+                if (binaries[j] == null) {
+                  buf.append("null");
+                } else {
+                  buf.append(new String(binaries[j], StandardCharsets.UTF_8));
+                }
+              }
+              buf.append("]");
+              break;
+            case DECIMAL: {
+              int precision = col.getTypeAttributes().getPrecision();
+              int scale = col.getTypeAttributes().getScale();
+              buf.append(Arrays.toString(
+                  ArrayCellViewHelper.toDecimalArray(arr, precision, scale)));
+              break;
+            }
+            case DATE:
+              
buf.append(Arrays.toString(ArrayCellViewHelper.toDateArray(arr)));
+              break;
+            case UNIXTIME_MICROS:
+              Timestamp[] tsArr = ArrayCellViewHelper.toTimestampArray(arr);
+              buf.append("[");
+              for (int j = 0; j < tsArr.length; j++) {
+                if (j > 0) {
+                  buf.append(", ");
+                }
+                buf.append(tsArr[j] == null ?
+                    "NULL"
+                    : TimestampUtil.timestampToString(tsArr[j]));
+              }
+              buf.append("]");
+              break;
+            default:
+              buf.append(arr);
+              break;
+          }
+          continue;
+        }
         switch (col.getType()) {
           case INT8:
             buf.append(getByte(i));
@@ -654,6 +745,133 @@ public abstract class RowResult {
     return buf.toString();
   }
 
+  // ----------------------------------------------------------------------
+  // Array column accessors
+  // ----------------------------------------------------------------------
+
+  /**
+   * Safely extract the serialized FlatBuffer bytes for an array column.
+   * <p>
+   * This method handles nulls and copies the correct slice of the
+   * underlying {@link ByteBuffer}. It is used internally by
+   * {@link #getArray(int)} and {@link #getArrayData(int)}.
+   *
+   * @param columnIndex Column index in the schema
+   * @return the serialized FlatBuffer bytes for the array cell,
+   *         or null if the cell itself is null
+   */
+  @Nullable
+  protected byte[] getArrayBytes(int columnIndex) {
+    ByteBuffer buf = getBinary(columnIndex);
+    if (buf == null || !buf.hasRemaining()) {
+      return null;
+    }
+    ByteBuffer dup = buf.duplicate();
+    byte[] raw = new byte[dup.remaining()];
+    dup.get(raw);
+    return raw;
+  }
+
+  /**
+   * Returns a lightweight FlatBuffer view of an array column.
+   * <p>
+   * The returned {@link ArrayCellView} provides element-level accessors,
+   * validity bits, and pretty-print formatting. No data copying or
+   * boxing occurs. Intended for internal use, debugging, or specialized
+   * integrations.
+   *
+   * <p>For application-level use, prefer {@link #getArrayData(int)},
+   * which returns a fully decoded Java array.
+   *
+   * @param columnIndex Column index in the schema
+   * @return an {@link ArrayCellView}, or null if the column is null
+   * @throws IllegalArgumentException if the column is not declared as an array
+   * @throws IndexOutOfBoundsException if the column doesn't exist
+   */
+  @InterfaceAudience.Private
+  ArrayCellView getArray(int columnIndex) {
+    checkValidColumn(columnIndex);
+    if (isNull(columnIndex)) {
+      return null;
+    }
+    ColumnSchema col = schema.getColumnByIndex(columnIndex);
+    if (!col.isArray()) {
+      throw new IllegalArgumentException(
+          String.format("Column (name: %s, index: %d) is not an array",
+              col.getName(), columnIndex));
+    }
+
+    byte[] raw = getArrayBytes(columnIndex);
+    if (raw == null) {
+      return null;
+    }
+    return new ArrayCellView(raw);
+  }
+
+  /**
+   * Convenience overload for {@link #getArray(int)} by column name.
+   */
+  @InterfaceAudience.Private
+  ArrayCellView getArray(String columnName) {
+    return getArray(this.schema.getColumnIndex(columnName));
+  }
+
+  /**
+   * Returns the specified array column as a plain Java array
+   * (boxed elements) decoded using the column schema's
+   * precision and scale attributes where applicable.
+   * <p>
+   * The returned object type depends on the array's element type:
+   * <ul>
+   *   <li>INT8[] -> {@code Byte[]}</li>
+   *   <li>INT32[] -> {@code Integer[]}</li>
+   *   <li>INT64[] -> {@code Long[]}</li>
+   *   <li>FLOAT[] -> {@code Float[]}</li>
+   *   <li>DOUBLE[] -> {@code Double[]}</li>
+   *   <li>BOOL[] -> {@code Boolean[]}</li>
+   *   <li>STRING[] / VARCHAR[] -> {@code String[]}</li>
+   *   <li>DATE[] -> {@code java.sql.Date[]}</li>
+   *   <li>UNIXTIME_MICROS[] -> {@code java.sql.Timestamp[]}</li>
+   *   <li>DECIMAL(p,s)[] -> {@code java.math.BigDecimal[]}</li>
+   * </ul>
+   *
+   * @param columnIndex Column index in the schema
+   * @return a boxed Java array, or null if the column cell is null
+   * @throws IllegalArgumentException if the column is not an array
+   * @throws IndexOutOfBoundsException if the column doesn't exist
+   */
+
+  @Nullable
+  public final Object getArrayData(int columnIndex) {
+    checkValidColumn(columnIndex);
+    if (isNull(columnIndex)) {
+      return null;
+    }
+
+    ColumnSchema col = schema.getColumnByIndex(columnIndex);
+    if (!col.isArray()) {
+      throw new IllegalArgumentException(
+          String.format("Column (name: %s, index: %d) is not an array",
+              col.getName(), columnIndex));
+    }
+
+    byte[] raw = getArrayBytes(columnIndex);
+    if (raw == null) {
+      return null;
+    }
+
+    ArrayCellView view = new ArrayCellView(raw);
+    return ArrayCellViewHelper.toJavaArray(view, col);
+  }
+
+  /**
+   * Convenience overload for {@link #getArrayData(int)} by column name.
+   */
+  @Nullable
+  public final Object getArrayData(String columnName) {
+    return getArrayData(this.schema.getColumnIndex(columnName));
+  }
+
   /**
    * @return a string describing the location of this row result within
    * the iterator as well as its data.
@@ -666,4 +884,21 @@ public abstract class RowResult {
     buf.append("}");
     return buf.toString();
   }
+
+  private static void appendArrayValues(StringBuilder sb,
+                                        ArrayCellView arr,
+                                        java.util.function.IntFunction<Object> 
getter) {
+    sb.append("[");
+    for (int j = 0; j < arr.length(); j++) {
+      if (j > 0) {
+        sb.append(", ");
+      }
+      if (!arr.isValid(j)) {
+        sb.append("NULL");
+      } else {
+        sb.append(getter.apply(j));
+      }
+    }
+    sb.append("]");
+  }
 }
diff --git 
a/java/kudu-client/src/main/java/org/apache/kudu/client/RowwiseRowResult.java 
b/java/kudu-client/src/main/java/org/apache/kudu/client/RowwiseRowResult.java
index aa54fd3a6..57e90a4d2 100644
--- 
a/java/kudu-client/src/main/java/org/apache/kudu/client/RowwiseRowResult.java
+++ 
b/java/kudu-client/src/main/java/org/apache/kudu/client/RowwiseRowResult.java
@@ -77,7 +77,8 @@ class RowwiseRowResult extends RowResult {
     // If the schema has nullables, we also add the offset for the null bitmap 
at the end.
     for (int i = 1; i < columnOffsetsSize; i++) {
       org.apache.kudu.ColumnSchema column = schema.getColumnByIndex(i - 1);
-      int previousSize = column.getTypeSize();
+      int previousSize;
+      previousSize = column.getTypeSize();
       columnOffsets[i] = previousSize + currentOffset;
       currentOffset += previousSize;
     }
@@ -276,12 +277,15 @@ class RowwiseRowResult extends RowResult {
     checkType(columnIndex, Type.STRING, Type.VARCHAR);
     // C++ puts a Slice in rowData which is 16 bytes long for simplicity, but 
we only support ints.
     long offset = getOffset(columnIndex);
-    long length = 
rowData.getLong(getCurrentRowDataOffsetForColumn(columnIndex) + 8);
+    long length = Bytes.getLong(
+        this.rowData.getRawArray(),
+        this.rowData.getRawOffset() + 
getCurrentRowDataOffsetForColumn(columnIndex) + 8);
     assert offset < Integer.MAX_VALUE;
     assert length < Integer.MAX_VALUE;
-    return Bytes.getString(indirectData.getRawArray(),
-            indirectData.getRawOffset() + (int)offset,
-            (int)length);
+    return Bytes.getString(
+        indirectData.getRawArray(),
+        indirectData.getRawOffset() + (int) offset,
+        (int) length);
   }
 
   /**
@@ -296,15 +300,17 @@ class RowwiseRowResult extends RowResult {
   public byte[] getBinaryCopy(int columnIndex) {
     checkValidColumn(columnIndex);
     checkNull(columnIndex);
-    // C++ puts a Slice in rowData which is 16 bytes long for simplicity,
-    // but we only support ints.
     long offset = getOffset(columnIndex);
-    long length = 
rowData.getLong(getCurrentRowDataOffsetForColumn(columnIndex) + 8);
+    long length = Bytes.getLong(
+        this.rowData.getRawArray(),
+        this.rowData.getRawOffset() + 
getCurrentRowDataOffsetForColumn(columnIndex) + 8);
     assert offset < Integer.MAX_VALUE;
     assert length < Integer.MAX_VALUE;
-    byte[] ret = new byte[(int)length];
-    System.arraycopy(indirectData.getRawArray(), indirectData.getRawOffset() + 
(int) offset,
-                     ret, 0, (int) length);
+    byte[] ret = new byte[(int) length];
+    System.arraycopy(
+        indirectData.getRawArray(),
+        indirectData.getRawOffset() + (int) offset,
+        ret, 0, (int) length);
     return ret;
   }
 
@@ -323,14 +329,25 @@ class RowwiseRowResult extends RowResult {
   public ByteBuffer getBinary(int columnIndex) {
     checkValidColumn(columnIndex);
     checkNull(columnIndex);
-    checkType(columnIndex, Type.BINARY);
-    // C++ puts a Slice in rowData which is 16 bytes long for simplicity,
-    // but we only support ints.
+
+    ColumnSchema col = schema.getColumnByIndex(columnIndex);
+    if (!(col.getType() == Type.BINARY ||
+        col.getType() == Type.STRING ||
+        col.getType() == Type.VARCHAR ||
+        col.isArray())) {
+      throw new IllegalArgumentException(
+          String.format("Column %s is not varlen", col.getName()));
+    }
+
     long offset = getOffset(columnIndex);
-    long length = 
rowData.getLong(getCurrentRowDataOffsetForColumn(columnIndex) + 8);
+    long length = Bytes.getLong(
+        this.rowData.getRawArray(),
+        this.rowData.getRawOffset() + 
getCurrentRowDataOffsetForColumn(columnIndex) + 8);
     assert offset < Integer.MAX_VALUE;
     assert length < Integer.MAX_VALUE;
-    return ByteBuffer.wrap(indirectData.getRawArray(), 
indirectData.getRawOffset() + (int) offset,
+    return ByteBuffer.wrap(
+        indirectData.getRawArray(),
+        indirectData.getRawOffset() + (int) offset,
         (int) length);
   }
 
diff --git 
a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java 
b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java
index c2640fe3d..4cb829584 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java
@@ -34,6 +34,7 @@ import static 
org.apache.kudu.test.ClientTestUtil.createSchemaWithNonUniqueKey;
 import static 
org.apache.kudu.test.ClientTestUtil.createSchemaWithTimestampColumns;
 import static org.apache.kudu.test.ClientTestUtil.getBasicCreateTableOptions;
 import static 
org.apache.kudu.test.ClientTestUtil.getBasicTableOptionsWithNonCoveredRange;
+import static org.apache.kudu.test.ClientTestUtil.getSchemaWithArrayTypes;
 import static org.apache.kudu.test.ClientTestUtil.scanTableToStrings;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertArrayEquals;
@@ -53,11 +54,14 @@ import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
 import java.security.cert.CertificateException;
 import java.sql.Date;
+import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -2204,4 +2208,396 @@ public class TestKuduClient {
     });
     assertTrue(e.getMessage().contains("Could not parse certificate"));
   }
+
+  /**
+   * Test inserting and retrieving array columns.
+   */
+  @Test
+  public void testArrayColumns() throws Exception {
+    Schema schema = getSchemaWithArrayTypes();
+    client.createTable(TABLE_NAME, schema, getBasicCreateTableOptions());
+
+    KuduSession session = client.newSession();
+    KuduTable table = client.openTable(TABLE_NAME);
+
+    for (int i = 0; i < 10; i++) {
+      Insert insert = table.newInsert();
+      PartialRow row = insert.getRow();
+
+      row.addInt("key", i);
+
+      // INT8 array (nullable elements)
+      byte[] int8Values = new byte[]{(byte) i, (byte) (i + 1), (byte) (i + 2)};
+      boolean[] int8Validity = new boolean[]{true, i % 2 == 0, true};
+      row.addArrayInt8("int8_arr", int8Values, int8Validity);
+
+      // BOOL array (nullable elements)
+      boolean[] boolValues = new boolean[]{true, i % 2 == 0, false};
+      boolean[] boolValidity = new boolean[]{true, false, true};
+      row.addArrayBool("bool_arr", boolValues, boolValidity);
+
+      // BINARY array (nullable elements)
+      byte[][] binValues = new byte[][]{
+          ("bin_" + i).getBytes(StandardCharsets.UTF_8),
+          null,
+          ("bin_last_" + i).getBytes(StandardCharsets.UTF_8)
+      };
+      row.addArrayBinary("binary_arr", binValues, null);
+
+      // INT32 array with some nulls
+      if (i % 3 == 0) {
+        row.setNull("int32_arr");
+      } else {
+        int[] values = new int[]{i, i + 1, i + 2};
+        boolean[] validity = new boolean[]{true, i % 2 == 0, true};
+        row.addArrayInt32("int32_arr", values, validity);
+      }
+
+      // STRING array
+      row.addArrayString("string_arr", new String[]{
+          "row" + i,
+          null,
+          "long_string_" + i
+      });
+
+      // DECIMAL array (scale = 2 for testing)
+      row.addArrayDecimal("decimal_arr", new BigDecimal[]{
+          BigDecimal.valueOf(123, 2),
+          null,
+          BigDecimal.valueOf(i * 100L, 2)
+      }, null);
+
+      // DATE array
+      row.addArrayDate("date_arr", new java.sql.Date[]{
+          DateUtil.epochDaysToSqlDate(i),
+          null,
+          DateUtil.epochDaysToSqlDate(i + 5)
+      });
+
+      // TIMESTAMP array
+      row.addArrayTimestamp("ts_arr", new Timestamp[]{
+          TimestampUtil.microsToTimestamp(i * 1000L),
+          null,
+          TimestampUtil.microsToTimestamp((i + 1) * 1000L)
+      });
+
+      // VARCHAR array
+      row.addArrayVarchar("varchar_arr", new String[]{
+          "row" + i,
+          null,
+          "varchar" + i
+      });
+
+      session.apply(insert);
+    }
+    session.flush();
+
+    // Scan back stringified form and verify logical output
+    List<String> rowStrings = scanTableToStrings(table);
+    assertEquals(10, rowStrings.size());
+
+    for (int i = 0; i < rowStrings.size(); i++) {
+      StringBuilder expectedRow = new StringBuilder();
+      expectedRow.append("INT32 key=").append(i);
+
+      // INT8[] column
+      expectedRow.append(", INT8[] int8_arr=[")
+          .append(i).append(", ")
+          .append(i % 2 == 0 ? i + 1 : "NULL").append(", ")
+          .append(i + 2).append("]");
+
+      // BOOL[] column
+      expectedRow.append(", BOOL[] bool_arr=[true, NULL, false]");
+
+      // BINARY[] column
+      expectedRow.append(", BINARY[] binary_arr=[bin_").append(i)
+          .append(", null, bin_last_").append(i).append("]");
+
+
+      // INT32[] column
+      expectedRow.append(", INT32[] int32_arr=");
+      if (i % 3 == 0) {
+        expectedRow.append("NULL");
+      } else {
+        expectedRow.append("[");
+        expectedRow.append(i); // always valid
+        expectedRow.append(", ");
+        if (i % 2 == 0) {
+          expectedRow.append(i + 1);
+        } else {
+          expectedRow.append("NULL");
+        }
+        expectedRow.append(", ");
+        expectedRow.append(i + 2);
+        expectedRow.append("]");
+      }
+
+      // STRING[] column
+      expectedRow.append(", STRING[] string_arr=[row").append(i)
+          .append(", null, long_string_").append(i).append("]");
+
+      // DECIMAL[] column
+      expectedRow.append(", DECIMAL[] decimal_arr(5, 2)=[1.23, null, ")
+          .append(BigDecimal.valueOf(i * 100L, 2)).append("]");
+
+      // DATE[] column
+      expectedRow.append(", DATE[] date_arr=[");
+      expectedRow.append(DateUtil.epochDaysToSqlDate(i));
+      expectedRow.append(", null, ");
+      expectedRow.append(DateUtil.epochDaysToSqlDate(i + 5));
+      expectedRow.append("]");
+
+      // TIMESTAMP[] column
+      expectedRow.append(", UNIXTIME_MICROS[] ts_arr=[");
+      expectedRow.append(TimestampUtil.timestampToString(
+          TimestampUtil.microsToTimestamp(i * 1000L)));
+      expectedRow.append(", NULL, ");
+      expectedRow.append(TimestampUtil.timestampToString(
+          TimestampUtil.microsToTimestamp((i + 1) * 1000L)));
+      expectedRow.append("]");
+
+      // VARCHAR[] column
+      expectedRow.append(", VARCHAR[] varchar_arr(10)=[row").append(i)
+          .append(", null, varchar").append(i).append("]");
+
+      assertEquals(expectedRow.toString(), rowStrings.get(i));
+    }
+
+    // Verify actual array decoding by logical type
+    KuduScanner scanner = client.newScannerBuilder(table).build();
+    while (scanner.hasMoreRows()) {
+      for (RowResult rr : scanner.nextRows()) {
+        int key = rr.getInt("key");
+
+        // INT8[]
+        Object int8Obj = rr.getArrayData("int8_arr");
+        assertTrue(int8Obj instanceof Byte[]);
+        Byte[] int8Vals = (Byte[]) int8Obj;
+        assertEquals(3, int8Vals.length);
+        assertEquals(Byte.valueOf((byte) key), int8Vals[0]);
+        if (key % 2 == 0) {
+          assertEquals(Byte.valueOf((byte) (key + 1)), int8Vals[1]);
+        } else {
+          assertNull(int8Vals[1]);
+        }
+        assertEquals(Byte.valueOf((byte) (key + 2)), int8Vals[2]);
+
+        // BOOL[]
+        Object boolObj = rr.getArrayData("bool_arr");
+        assertTrue(boolObj instanceof Boolean[]);
+        Boolean[] boolVals = (Boolean[]) boolObj;
+        assertEquals(3, boolVals.length);
+        assertEquals(Boolean.TRUE, boolVals[0]);
+        assertNull(boolVals[1]);
+        assertEquals(Boolean.FALSE, boolVals[2]);
+
+        // BINARY[]
+        Object binObj = rr.getArrayData("binary_arr");
+        assertTrue(binObj instanceof byte[][]);
+        byte[][] binVals = (byte[][]) binObj;
+        assertEquals(3, binVals.length);
+        assertEquals(("bin_" + key), new String(binVals[0], 
StandardCharsets.UTF_8));
+        assertNull(binVals[1]);
+        assertEquals(("bin_last_" + key), new String(binVals[2], 
StandardCharsets.UTF_8));
+
+        // INT32[] -> Integer[]
+        Object intObj = rr.getArrayData("int32_arr");
+        if (key % 3 == 0) {
+          // Entire array was intentionally NULL
+          assertNull(intObj);
+        } else {
+          assertTrue(intObj instanceof Integer[]);
+          Integer[] intVals = (Integer[]) intObj;
+          assertEquals(3, intVals.length);
+          assertEquals(Integer.valueOf(key), intVals[0]);
+          if (key % 2 == 0) {
+            assertEquals(Integer.valueOf(key + 1), intVals[1]);
+          } else {
+            assertNull(intVals[1]);
+          }
+          assertEquals(Integer.valueOf(key + 2), intVals[2]);
+        }
+
+        // STRING[] -> String[]
+        Object strObj = rr.getArrayData("string_arr");
+        assertTrue(strObj instanceof String[]);
+        String[] strVals = (String[]) strObj;
+        assertEquals(3, strVals.length);
+        assertEquals("row" + key, strVals[0]);
+        assertNull(strVals[1]);
+        assertEquals("long_string_" + key, strVals[2]);
+
+        // DECIMAL[] -> BigDecimal[]
+        Object decObj = rr.getArrayData("decimal_arr");
+        assertTrue(decObj instanceof BigDecimal[]);
+        BigDecimal[] decVals = (BigDecimal[]) decObj;
+        assertEquals(3, decVals.length);
+        assertEquals(new BigDecimal("1.23"), decVals[0]);
+        assertNull(decVals[1]);
+        assertEquals(BigDecimal.valueOf(key * 100L, 2), decVals[2]);
+
+        // DATE[] -> java.sql.Date[]
+        Object dateObj = rr.getArrayData("date_arr");
+        assertTrue(dateObj instanceof java.sql.Date[]);
+        java.sql.Date[] dateVals = (java.sql.Date[]) dateObj;
+        assertEquals(3, dateVals.length);
+        assertEquals(DateUtil.epochDaysToSqlDate(key), dateVals[0]);
+        assertNull(dateVals[1]);
+        assertEquals(DateUtil.epochDaysToSqlDate(key + 5), dateVals[2]);
+
+        // TIMESTAMP[] -> java.sql.Timestamp[]
+        Object tsObj = rr.getArrayData("ts_arr");
+        assertTrue(tsObj instanceof java.sql.Timestamp[]);
+        java.sql.Timestamp[] tsVals = (java.sql.Timestamp[]) tsObj;
+        assertEquals(3, tsVals.length);
+        assertEquals(TimestampUtil.microsToTimestamp(key * 1000L), tsVals[0]);
+        assertNull(tsVals[1]);
+        assertEquals(TimestampUtil.microsToTimestamp((key + 1) * 1000L), 
tsVals[2]);
+
+        // VARCHAR[] -> String[]
+        Object vchObj = rr.getArrayData("varchar_arr");
+        assertTrue(vchObj instanceof String[]);
+        String[] vchVals = (String[]) vchObj;
+        assertEquals(3, vchVals.length);
+        assertEquals("row" + key, vchVals[0]);
+        assertNull(vchVals[1]);
+        assertEquals("varchar" + key, vchVals[2]);
+      }
+
+    }
+  }
+
+  @Test
+  public void testArrayEmptyValidityOptimization() throws Exception {
+    Schema schema = getSchemaWithArrayTypes();
+    client.createTable(TABLE_NAME, schema, getBasicCreateTableOptions());
+    KuduTable table = client.openTable(TABLE_NAME);
+    final KuduSession session = client.newSession();
+
+    Insert insert = table.newInsert();
+    PartialRow row = insert.getRow();
+    row.addInt("key", 100);
+
+    // INT8[]
+    row.addArrayInt8("int8_arr", new byte[]{1, 2, 3}, new boolean[0]);
+
+    // DECIMAL[]
+    row.addArrayDecimal("decimal_arr",
+        new BigDecimal[]{new BigDecimal("1.23"), new BigDecimal("4.56"), new 
BigDecimal("7.89")},
+        new boolean[0]);  // all valid
+
+    // DATE[]
+    row.addArrayDate("date_arr", new Date[]{
+        DateUtil.epochDaysToSqlDate(1),
+        DateUtil.epochDaysToSqlDate(2),
+        DateUtil.epochDaysToSqlDate(3)
+    }, new boolean[0]);
+
+    // TIMESTAMP[]
+    row.addArrayTimestamp("ts_arr", new Timestamp[]{
+        TimestampUtil.microsToTimestamp(1000L),
+        TimestampUtil.microsToTimestamp(2000L),
+        TimestampUtil.microsToTimestamp(3000L)
+    }, new boolean[0]);
+
+    // VARCHAR[]
+    row.addArrayVarchar("varchar_arr", new String[]{"x", "y", "z"}, new 
boolean[0]);
+
+    // Optional nullable ones can be left null
+    row.setNull("bool_arr");
+    row.setNull("binary_arr");
+    row.setNull("int32_arr");
+    row.setNull("string_arr");
+
+    session.apply(insert);
+
+    // Insert second row with null validity vector
+    Insert insert2 = table.newInsert();
+    PartialRow row2 = insert2.getRow();
+    row2.addInt("key", 200);
+    row2.addArrayInt8("int8_arr", new byte[]{1, 2, 3});
+    row2.addArrayDecimal("decimal_arr",
+        new BigDecimal[]{new BigDecimal("1.23"), null, new BigDecimal("7.89")},
+        null);
+    row2.addArrayDate("date_arr", new Date[]{
+        DateUtil.epochDaysToSqlDate(10),
+        DateUtil.epochDaysToSqlDate(11),
+        DateUtil.epochDaysToSqlDate(12)
+    });
+    row2.addArrayTimestamp("ts_arr", new Timestamp[]{
+        TimestampUtil.microsToTimestamp(10_000L),
+        TimestampUtil.microsToTimestamp(20_000L),
+        TimestampUtil.microsToTimestamp(30_000L)
+    });
+    row2.addArrayVarchar("varchar_arr", new String[]{"a", null, "c"});
+    row2.setNull("bool_arr");
+    row2.setNull("binary_arr");
+    row2.setNull("int32_arr");
+    row2.setNull("string_arr");
+
+    session.apply(insert2);
+    session.flush();
+
+    // --- Verify readback ---
+    KuduScanner scanner = client.newScannerBuilder(table).build();
+    List<RowResult> rows = new ArrayList<>();
+    while (scanner.hasMoreRows()) {
+      RowResultIterator it = scanner.nextRows();
+      while (it.hasNext()) {
+        rows.add(it.next());
+      }
+    }
+    assertEquals(2, rows.size());
+    RowResult r1 = rows.get(0);
+
+    // INT8[]
+    Byte[] int8Vals = (Byte[]) r1.getArrayData("int8_arr");
+    assertArrayEquals(new Byte[]{1, 2, 3}, int8Vals);
+
+    // DECIMAL[]
+    BigDecimal[] decVals = (BigDecimal[]) r1.getArrayData("decimal_arr");
+    assertArrayEquals(
+        new BigDecimal[]{new BigDecimal("1.23"), new BigDecimal("4.56"), new 
BigDecimal("7.89")},
+        decVals);
+
+    // DATE[]
+    Date[] dateVals = (Date[]) r1.getArrayData("date_arr");
+    assertEquals(DateUtil.epochDaysToSqlDate(1), dateVals[0]);
+    assertEquals(DateUtil.epochDaysToSqlDate(3), dateVals[2]);
+
+    // TIMESTAMP[]
+    Timestamp[] tsVals = (Timestamp[]) r1.getArrayData("ts_arr");
+    assertEquals(TimestampUtil.microsToTimestamp(1000L), tsVals[0]);
+    assertEquals(TimestampUtil.microsToTimestamp(3000L), tsVals[2]);
+
+    // VARCHAR[]
+    String[] vcharVals = (String[]) r1.getArrayData("varchar_arr");
+    assertArrayEquals(new String[]{"x", "y", "z"}, vcharVals);
+
+    RowResult r2 = rows.get(1);
+
+    // INT8[]
+    Byte[] int8Vals2 = (Byte[]) r2.getArrayData("int8_arr");
+    assertArrayEquals(new Byte[]{1, 2, 3}, int8Vals2);
+
+    // DECIMAL[]
+    BigDecimal[] decVals2 = (BigDecimal[]) r2.getArrayData("decimal_arr");
+    assertArrayEquals(
+        new BigDecimal[]{new BigDecimal("1.23"), null, new BigDecimal("7.89")},
+        decVals2);
+
+    // DATE[]
+    Date[] dateVals2 = (Date[]) r2.getArrayData("date_arr");
+    assertEquals(DateUtil.epochDaysToSqlDate(10), dateVals2[0]);
+    assertEquals(DateUtil.epochDaysToSqlDate(12), dateVals2[2]);
+
+    // TIMESTAMP[]
+    Timestamp[] tsVals2 = (Timestamp[]) r2.getArrayData("ts_arr");
+    assertEquals(TimestampUtil.microsToTimestamp(10_000L), tsVals2[0]);
+    assertEquals(TimestampUtil.microsToTimestamp(30_000L), tsVals2[2]);
+
+    // VARCHAR[]
+    String[] vcharVals2 = (String[]) r2.getArrayData("varchar_arr");
+    assertArrayEquals(new String[]{"a", null, "c"}, vcharVals2);
+  }
 }
diff --git 
a/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartialRow.java 
b/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartialRow.java
index 48ccae9d9..6bc189a3b 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartialRow.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartialRow.java
@@ -264,9 +264,7 @@ public class TestPartialRow {
     ArrayCellView view = (ArrayCellView) row.getObject("strings");
     assertEquals(3, view.length());
     assertEquals("foo", view.getString(0));
-
     assertFalse(view.isValid(1));
-    assertNull(view.getString(1));
     assertEquals("bar", view.getString(2));
   }
 
diff --git 
a/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowResult.java 
b/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowResult.java
index 2624cc391..f7c0544ba 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowResult.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowResult.java
@@ -19,14 +19,18 @@ package org.apache.kudu.client;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static 
org.apache.kudu.test.ClientTestUtil.getAllTypesCreateTableOptions;
+import static 
org.apache.kudu.test.ClientTestUtil.getArrayTypesCreateTableOptions;
 import static org.apache.kudu.test.ClientTestUtil.getSchemaWithAllTypes;
+import static org.apache.kudu.test.ClientTestUtil.getSchemaWithArrayTypes;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.math.BigDecimal;
 import java.nio.ByteBuffer;
+import java.sql.Date;
 import java.sql.Timestamp;
 
 import org.junit.Before;
@@ -218,4 +222,139 @@ public class TestRowResult {
       }
     }
   }
+
+  @Test
+  public void testArrayColumns() throws Exception {
+    String tableName = "TestRowResult-Arrays-" + System.currentTimeMillis();
+    Schema schema = getSchemaWithArrayTypes();
+    harness.getClient().createTable(tableName, schema, 
getArrayTypesCreateTableOptions());
+    KuduTable table = harness.getClient().openTable(tableName);
+
+    final KuduSession session = harness.getClient().newSession();
+    Insert insert = table.newInsert();
+    PartialRow row = insert.getRow();
+    row.addInt("key", 1);
+
+    row.addArrayInt8(schema.getColumnIndex("int8_arr"),
+        new byte[]{1, 2, 3}, new boolean[]{true, false, true});
+
+    row.addArrayBool(schema.getColumnIndex("bool_arr"),
+        new boolean[]{true, false, true}, new boolean[]{true, false, true});
+
+    row.addArrayBinary(schema.getColumnIndex("binary_arr"),
+        new byte[][]{"a".getBytes(UTF_8), "b".getBytes(UTF_8), 
"c".getBytes(UTF_8)},
+        new boolean[]{true, false, true});
+
+    row.addArrayInt32(schema.getColumnIndex("int32_arr"), new int[]{1, 2, 3});
+    row.addArrayString(schema.getColumnIndex("string_arr"),
+        new String[]{"a", null, "c"});
+
+    row.addArrayDecimal(schema.getColumnIndex("decimal_arr"),
+        new BigDecimal[]{BigDecimal.valueOf(123, 2), null, 
BigDecimal.valueOf(456, 2)},
+        /*validity*/ null);
+
+    row.addArrayDate(schema.getColumnIndex("date_arr"),
+        new Date[]{DateUtil.epochDaysToSqlDate(0), 
DateUtil.epochDaysToSqlDate(10)});
+
+    row.addArrayTimestamp(schema.getColumnIndex("ts_arr"),
+        new Timestamp[]{new Timestamp(1000), new Timestamp(2000)});
+
+    row.addArrayVarchar(schema.getColumnIndex("varchar_arr"),
+        new String[]{"abc", "xyz"}, /*validity*/ null);
+
+    session.apply(insert);
+
+    // Scan back
+    KuduScanner scanner = harness.getClient().newScannerBuilder(table).build();
+    RowResult rr = scanner.nextRows().next();
+
+    // int8[]
+    ArrayCellView int8Arr = rr.getArray("int8_arr");
+    assertEquals(3, int8Arr.length());
+    assertEquals(1, int8Arr.getInt8(0));
+    assertFalse(int8Arr.isValid(1));
+    assertEquals(3, int8Arr.getInt8(2));
+
+    // bool[]
+    ArrayCellView boolArr = rr.getArray("bool_arr");
+    assertEquals(3, boolArr.length());
+    assertTrue(boolArr.getBoolean(0));
+    assertFalse(boolArr.isValid(1));
+    assertTrue(boolArr.getBoolean(2));
+
+    // binary[]
+    ArrayCellView binArr = rr.getArray("binary_arr");
+    assertEquals(3, binArr.length());
+    assertArrayEquals("a".getBytes(UTF_8), binArr.getBinary(0));
+    assertFalse(binArr.isValid(1));
+    assertArrayEquals("c".getBytes(UTF_8), binArr.getBinary(2));
+
+    // int32[]
+    ArrayCellView intArr = rr.getArray("int32_arr");
+    assertEquals(3, intArr.length());
+    assertEquals(1, intArr.getInt32(0));
+    assertEquals(2, intArr.getInt32(1));
+    assertEquals(3, intArr.getInt32(2));
+
+    // string[]
+    ArrayCellView strArr = rr.getArray("string_arr");
+    assertEquals(3, strArr.length());
+    assertEquals("a", strArr.getString(0));
+    assertFalse(strArr.isValid(1));
+    assertEquals("c", strArr.getString(2));
+
+    // decimal[]
+    BigDecimal[] decimals = 
ArrayCellViewHelper.toDecimalArray(rr.getArray("decimal_arr"), 9, 2);
+    assertEquals(new BigDecimal("1.23"), decimals[0]);
+    assertNull(decimals[1]);
+    assertEquals(new BigDecimal("4.56"), decimals[2]);
+
+    // date[]
+    Date[] dates = ArrayCellViewHelper.toDateArray(rr.getArray("date_arr"));
+    assertEquals(DateUtil.epochDaysToSqlDate(0), dates[0]);
+    assertEquals(DateUtil.epochDaysToSqlDate(10), dates[1]);
+
+    // timestamp[]
+    Timestamp[] ts = 
ArrayCellViewHelper.toTimestampArray(rr.getArray("ts_arr"));
+    assertEquals(new Timestamp(1000), ts[0]);
+    assertEquals(new Timestamp(2000), ts[1]);
+
+    // varchar[]
+    String[] vch = 
ArrayCellViewHelper.toVarcharArray(rr.getArray("varchar_arr"));
+    assertArrayEquals(new String[]{"abc", "xyz"}, vch);
+
+    // Verify schema-aware conversions
+    Byte[] int8Boxed = (Byte[]) rr.getArrayData("int8_arr");
+    assertArrayEquals(new Byte[]{1, null, 3}, int8Boxed);
+
+    Integer[] intBoxed = (Integer[]) rr.getArrayData("int32_arr");
+    assertArrayEquals(new Integer[]{1, 2, 3}, intBoxed);
+
+    String[] strBoxed = (String[]) rr.getArrayData("string_arr");
+    assertArrayEquals(new String[]{"a", null, "c"}, strBoxed);
+
+    BigDecimal[] decBoxed = (BigDecimal[]) rr.getArrayData("decimal_arr");
+    assertEquals(new BigDecimal("1.23"), decBoxed[0]);
+    assertNull(decBoxed[1]);
+    assertEquals(new BigDecimal("4.56"), decBoxed[2]);
+
+    Date[] dateBoxed = (Date[]) rr.getArrayData("date_arr");
+    assertEquals(DateUtil.epochDaysToSqlDate(0), dateBoxed[0]);
+    assertEquals(DateUtil.epochDaysToSqlDate(10), dateBoxed[1]);
+
+    Timestamp[] tsBoxed = (Timestamp[]) rr.getArrayData("ts_arr");
+    assertEquals(new Timestamp(1000), tsBoxed[0]);
+    assertEquals(new Timestamp(2000), tsBoxed[1]);
+
+    String[] vchBoxed = (String[]) rr.getArrayData("varchar_arr");
+    assertArrayEquals(new String[]{"abc", "xyz"}, vchBoxed);
+
+    Boolean[] boolBoxed = (Boolean[]) rr.getArrayData("bool_arr");
+    assertArrayEquals(new Boolean[]{true, null, true}, boolBoxed);
+
+    byte[][] binBoxed = (byte[][]) rr.getArrayData("binary_arr");
+    assertArrayEquals("a".getBytes(UTF_8), binBoxed[0]);
+    assertNull(binBoxed[1]);
+    assertArrayEquals("c".getBytes(UTF_8), binBoxed[2]);
+  }
 }
diff --git 
a/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ClientTestUtil.java 
b/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ClientTestUtil.java
index 888a12b8b..a7ff57e93 100644
--- 
a/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ClientTestUtil.java
+++ 
b/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ClientTestUtil.java
@@ -228,6 +228,33 @@ public abstract class ClientTestUtil {
     return new Schema(columns);
   }
 
+  public static Schema getSchemaWithArrayTypes() {
+    List<ColumnSchema> columns = ImmutableList.of(
+        new ColumnSchema.ColumnSchemaBuilder("key", 
Type.INT32).key(true).build(),
+        new ColumnSchema.ColumnSchemaBuilder("int8_arr", Type.INT8)
+            .array(true).nullable(true).build(),
+        new ColumnSchema.ColumnSchemaBuilder("bool_arr", Type.BOOL)
+            .array(true).nullable(true).build(),
+        new ColumnSchema.ColumnSchemaBuilder("binary_arr", Type.BINARY)
+            .array(true).nullable(true).build(),
+        new ColumnSchema.ColumnSchemaBuilder("int32_arr", Type.INT32)
+            .array(true).nullable(true).build(),
+        new ColumnSchema.ColumnSchemaBuilder("string_arr", Type.STRING)
+            .array(true).nullable(true).build(),
+        new ColumnSchema.ColumnSchemaBuilder("decimal_arr", Type.DECIMAL)
+            .typeAttributes(DecimalUtil.typeAttributes(5, 2))
+            .array(true).nullable(true).build(),
+        new ColumnSchema.ColumnSchemaBuilder("date_arr", Type.DATE)
+            .array(true).build(),
+        new ColumnSchema.ColumnSchemaBuilder("ts_arr", Type.UNIXTIME_MICROS)
+            .array(true).build(),
+        new ColumnSchema.ColumnSchemaBuilder("varchar_arr", Type.VARCHAR)
+            .typeAttributes(CharUtil.typeAttributes(10))
+            .array(true).build()
+    );
+    return new Schema(columns);
+  }
+
   public static PartialRow getPartialRowWithAllTypes(PartialRow row, byte 
int8Value) {
     if (row == null) {
       Schema schema = getSchemaWithAllTypes();
@@ -264,6 +291,11 @@ public abstract class ClientTestUtil {
     return new 
CreateTableOptions().setRangePartitionColumns(ImmutableList.of("int8"));
   }
 
+  public static CreateTableOptions getArrayTypesCreateTableOptions() {
+    return new CreateTableOptions()
+        .setRangePartitionColumns(ImmutableList.of("key"));
+  }
+
   public static Schema getBasicSchema() {
     ArrayList<ColumnSchema> columns = new ArrayList<>(5);
     columns.add(new ColumnSchema.ColumnSchemaBuilder("key", 
Type.INT32).key(true).build());

Reply via email to