Repository: apache/parquet-java (2026-07-02T07:09:05Z)

Issue:
Throw IAE for odd-length hex strings in Parquet CLI
### Describe the bug, including details regarding any error messages, version, and platform.

`BaseCommand` currently doesn't do any sort of validation and accepts odd-length hex strings, which cause `IndexOutOfBoundsExceptions`.

### Component(s)

CLI


Discussion:

Patch:
diff --git a/parquet-cli/src/main/java/org/apache/parquet/cli/BaseCommand.java b/parquet-cli/src/main/java/org/apache/parquet/cli/BaseCommand.java
index 0c8841b05c..9e12102e02 100644
--- a/parquet-cli/src/main/java/org/apache/parquet/cli/BaseCommand.java
+++ b/parquet-cli/src/main/java/org/apache/parquet/cli/BaseCommand.java
@@ -20,6 +20,7 @@
 package org.apache.parquet.cli;
 
 import com.beust.jcommander.internal.Lists;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import com.google.common.io.CharStreams;
 import com.google.common.io.Resources;
@@ -428,16 +429,22 @@ private void parseAndSetColumnKeys(String columnKeysStr, ConfigurableKeyRetrieve
     }
   }
 
-  private byte[] hexToBytes(String hex) {
-
+  @VisibleForTesting
+  byte[] hexToBytes(String hex) {
+    String originalHex = hex;
     if (hex.startsWith("0x") || hex.startsWith("0X")) {
       hex = hex.substring(2);
     }
 
     int len = hex.length();
+    Preconditions.checkArgument(len % 2 == 0, "Invalid hex string: %s", originalHex);
+
     byte[] data = new byte[len / 2];
     for (int i = 0; i < len; i += 2) {
-      data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16));
+      int high = Character.digit(hex.charAt(i), 16);
+      int low = Character.digit(hex.charAt(i + 1), 16);
+      Preconditions.checkArgument(high >= 0 && low >= 0, "Invalid hex string: %s", originalHex);
+      data[i / 2] = (byte) ((high << 4) + low);
     }
     return data;
   }


Test patch:
diff --git a/parquet-cli/src/test/java/org/apache/parquet/cli/BaseCommandTest.java b/parquet-cli/src/test/java/org/apache/parquet/cli/BaseCommandTest.java
index 1f441c3348..731f799e1c 100644
--- a/parquet-cli/src/test/java/org/apache/parquet/cli/BaseCommandTest.java
+++ b/parquet-cli/src/test/java/org/apache/parquet/cli/BaseCommandTest.java
@@ -18,6 +18,9 @@
  */
 package org.apache.parquet.cli;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
 import java.io.IOException;
 import java.net.URI;
 import java.util.List;
@@ -62,6 +65,29 @@ public void qualifiedURIResourceURITest() throws IOException {
     Assert.assertEquals("/a", uri.getPath());
   }
 
+  @Test
+  public void hexToBytes() {
+    assertThat(this.command.hexToBytes("0x10")).containsExactly(0x10);
+    assertThat(this.command.hexToBytes("0x0506")).containsExactly(0x05, 0x06);
+    assertThat(this.command.hexToBytes("0506")).containsExactly(0x05, 0x06);
+    assertThat(this.command.hexToBytes("0x010203")).containsExactly(0x01, 0x02, 0x03);
+  }
+
+  @Test
+  public void hexToBytesRejectsInvalidHexString() {
+    assertThatThrownBy(() -> this.command.hexToBytes("0x011"))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Invalid hex string: 0x011");
+
+    assertThatThrownBy(() -> this.command.hexToBytes("0xABZZ"))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Invalid hex string: 0xABZZ");
+
+    assertThatThrownBy(() -> this.command.hexToBytes("0xabgg"))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Invalid hex string: 0xabgg");
+  }
+
   // For Windows
   @Test
   public void qualifiedPathTestForWindows() throws IOException {