This is an automated email from the ASF dual-hosted git repository.
Abacn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push:
new 837590e5426 Fix Row.toString NPE on a null nested inside an array, map
or row (#39587)
837590e5426 is described below
commit 837590e542605e870e0a6653edf408ffad7f09a2
Author: ZIHAN DAI <[email protected]>
AuthorDate: Sat Aug 8 01:10:31 2026 +1000
Fix Row.toString NPE on a null nested inside an array, map or row (#39587)
toPrettyRowString drops a row's own null fields, but every recursive call
hands the raw element to toPrettyFieldValueString, which dereferences it:
a null String hits string.replace, a null Row hits row.getValues, and a
null array or map hits value.getClass. Only the numeric and boolean
branches survive, because Objects.toString already renders null as "null".
Return "null" for a null value instead, which is what those numeric
branches have always produced.
Row.toString has routed through this printer since 2.69.0, so the NPE
fires from logging, PAssert messages and debugger inspection - usually
while a pipeline is already reporting a different problem.
Fixes #21063
---
.../org/apache/beam/sdk/schemas/SchemaUtils.java | 7 ++++
.../apache/beam/sdk/schemas/SchemaUtilsTest.java | 43 ++++++++++++++++++++++
2 files changed, 50 insertions(+)
diff --git
a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/SchemaUtils.java
b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/SchemaUtils.java
index c8773ce2c23..88809182efc 100644
--- a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/SchemaUtils.java
+++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/SchemaUtils.java
@@ -238,6 +238,13 @@ public class SchemaUtils {
}
static String toPrettyFieldValueString(Schema.FieldType fieldType, Object
value, String prefix) {
+ if (value == null) {
+ // toPrettyRowString drops a row's own null fields, but a null nested
inside an array, an
+ // iterable, a map or a row has a position and has to be rendered. The
numeric and boolean
+ // branches below already render one as "null" via Objects.toString, so
do the same for the
+ // remaining types rather than dereferencing the value.
+ return "null";
+ }
String nextPrefix = prefix + INDENT;
switch (fieldType.getTypeName()) {
case BYTE:
diff --git
a/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/SchemaUtilsTest.java
b/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/SchemaUtilsTest.java
index 45c06f65aaa..c34832f58fb 100644
---
a/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/SchemaUtilsTest.java
+++
b/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/SchemaUtilsTest.java
@@ -18,8 +18,13 @@
package org.apache.beam.sdk.schemas;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
import org.apache.beam.sdk.schemas.Schema.FieldType;
+import org.apache.beam.sdk.values.Row;
import org.junit.Test;
/** Tests for {@link org.apache.beam.sdk.schemas.SchemaUtils}. */
@@ -104,4 +109,42 @@ public class SchemaUtilsTest {
.build();
assertEquals(expected, SchemaUtils.mergeWideningNullable(schema1,
schema2));
}
+
+ @Test
+ public void testToPrettyStringRendersNullInsideArray() {
+ Schema schema =
+ Schema.builder().addArrayField("a",
FieldType.STRING.withNullable(true)).build();
+ Row row = Row.withSchema(schema).addValue(Arrays.asList("x",
null)).build();
+ assertTrue(row.toString(), row.toString().contains("null"));
+ }
+
+ @Test
+ public void testToPrettyStringRendersNullMapValue() {
+ Schema schema =
+ Schema.builder()
+ .addMapField("m", FieldType.STRING,
FieldType.STRING.withNullable(true))
+ .build();
+ Map<String, String> map = new HashMap<>();
+ map.put("k", null);
+ Row row = Row.withSchema(schema).addValue(map).build();
+ assertTrue(row.toString(), row.toString().contains("null"));
+ }
+
+ @Test
+ public void testToPrettyStringRendersNullRowInsideArray() {
+ Schema inner = Schema.builder().addStringField("s").build();
+ Schema schema =
+ Schema.builder().addArrayField("a",
FieldType.row(inner).withNullable(true)).build();
+ Row row = Row.withSchema(schema).addValue(Arrays.asList((Row)
null)).build();
+ assertTrue(row.toString(), row.toString().contains("null"));
+ }
+
+ @Test
+ public void testToPrettyStringRendersNullInsideArrayOfInts() {
+ Schema schema = Schema.builder().addArrayField("a",
FieldType.INT32.withNullable(true)).build();
+ Row row = Row.withSchema(schema).addValue(Arrays.asList(1, null)).build();
+ String rendered = row.toString();
+ assertTrue(rendered, rendered.contains("1"));
+ assertTrue(rendered, rendered.contains("null"));
+ }
}