Repository: provectus/kafka-ui
Issue: Error while trying to read messages from _schemas topic
Labels: type/bug
scope/backend

Kafka UI seems to be unable to read messages from SchemaRegistry's _schemas topic.
### How to recreate the issue
1) Run docker-compose file (with kafka-ui) from master branch (current commit is 73f8991)
2) Go to the cluster "local" -> Topics -> _schemas -> Messages

### Expected behaviour:
Messages' content and metadata appear 

### Actual behaviour:
API crushes with `java.lang.NullPointerException` and the loading spinner never goes away

### Logs:
[kafka_ui_bug.log](https://github.com/provectus/kafka-ui/files/6064186/kafka_ui_bug.log)

I'm not sure if it is a real bug or is it just because SchemaRegistry operations are in WIP, but it'd be great to add some sort of error message on the frontend side nevertheless.

Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/deserialization/SchemaRegistryRecordDeserializer.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/deserialization/SchemaRegistryRecordDeserializer.java
index 00544a109dc..a9e3887dbbf 100644
--- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/deserialization/SchemaRegistryRecordDeserializer.java
+++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/cluster/deserialization/SchemaRegistryRecordDeserializer.java
@@ -150,7 +150,7 @@ private Object parseAvroRecord(ConsumerRecord<Bytes, Bytes> record) throws IOExc
 			byte[] bytes = AvroSchemaUtils.toJson(avroRecord);
 			return parseJson(bytes);
 		} else {
-			return new HashMap<String,Object>();
+			return Map.of();
 		}
 	}
 
@@ -162,12 +162,16 @@ private Object parseProtobufRecord(ConsumerRecord<Bytes, Bytes> record) throws I
 			byte[] bytes = ProtobufSchemaUtils.toJson(message);
 			return parseJson(bytes);
 		} else {
-			return new HashMap<String,Object>();
+			return Map.of();
 		}
 	}
 
 	private Object parseJsonRecord(ConsumerRecord<Bytes, Bytes> record) throws IOException {
-		byte[] valueBytes = record.value().get();
+		var value = record.value();
+		if (value == null) {
+			return Map.of();
+		}
+		byte[] valueBytes = value.get();
 		return parseJson(valueBytes);
 	}
 
@@ -178,6 +182,9 @@ private Object parseJson(byte[] bytes) throws IOException {
 
 	private Object parseStringRecord(ConsumerRecord<Bytes, Bytes> record) {
 		String topic = record.topic();
+		if (record.value() == null) {
+			return Map.of();
+		}
 		byte[] valueBytes = record.value().get();
 		return stringDeserializer.deserialize(topic, valueBytes);
 	}


Test patch:
diff --git a/kafka-ui-api/src/test/java/com/provectus/kafka/ui/cluster/deserialization/SchemaRegistryRecordDeserializerTest.java b/kafka-ui-api/src/test/java/com/provectus/kafka/ui/cluster/deserialization/SchemaRegistryRecordDeserializerTest.java
new file mode 100644
index 00000000000..3dbc716b21c
--- /dev/null
+++ b/kafka-ui-api/src/test/java/com/provectus/kafka/ui/cluster/deserialization/SchemaRegistryRecordDeserializerTest.java
@@ -0,0 +1,34 @@
+package com.provectus.kafka.ui.cluster.deserialization;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.provectus.kafka.ui.cluster.model.KafkaCluster;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.apache.kafka.common.utils.Bytes;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class SchemaRegistryRecordDeserializerTest {
+
+    private final SchemaRegistryRecordDeserializer deserializer = new SchemaRegistryRecordDeserializer(
+            KafkaCluster.builder()
+                    .schemaNameTemplate("%s-value")
+                    .build(),
+            new ObjectMapper()
+    );
+
+    @Test
+    public void shouldDeserializeStringValue() {
+        var value = "test";
+        var deserializedRecord = deserializer.deserialize(new ConsumerRecord<>("topic", 1, 0, Bytes.wrap("key".getBytes()), Bytes.wrap(value.getBytes())));
+        assertEquals(value, deserializedRecord);
+    }
+
+    @Test
+    public void shouldDeserializeNullValueRecordToEmptyMap() {
+        var deserializedRecord = deserializer.deserialize(new ConsumerRecord<>("topic", 1, 0, Bytes.wrap("key".getBytes()), null));
+        assertEquals(Map.of(), deserializedRecord);
+    }
+}
\ No newline at end of file