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

oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-kafka-connector.git


The following commit(s) were added to refs/heads/main by this push:
     new efe92f7b1f Fix #269: pass null keys and values through 
CamelTypeConverterTransform (#1855)
efe92f7b1f is described below

commit efe92f7b1f77e301e845d94988efed30ebf275b1
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Nov 5 11:36:18 2026 +0100

    Fix #269: pass null keys and values through CamelTypeConverterTransform 
(#1855)
    
    CamelTypeConverterTransform failed on a tombstone (a record with a null
    value) with a DataException, because the Camel type converter returns
    null for a null input. It also failed with a NullPointerException on any
    schemaless record, because it always copied the original schema.
    
    - Return the record unchanged when the key or value to convert is null,
      as Kafka's Cast SMT does.
    - Keep schemaless records schemaless instead of copying a null schema.
    - Add a reference page for the SMT and list it in the nav and in the
      transformers index.
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    Co-authored-by: Claude Opus 5.5 <[email protected]>
---
 .../transforms/CamelTypeConverterTransform.java    |  9 +++-
 .../CamelTypeConverterTransformTest.java           | 51 ++++++++++++++++++++++
 docs/modules/ROOT/nav.adoc                         |  1 +
 .../reference/transformers/camelTypeConverter.adoc | 44 +++++++++++++++++++
 .../ROOT/pages/reference/transformers/index.adoc   |  3 ++
 5 files changed, 106 insertions(+), 2 deletions(-)

diff --git 
a/core/src/main/java/org/apache/camel/kafkaconnector/transforms/CamelTypeConverterTransform.java
 
b/core/src/main/java/org/apache/camel/kafkaconnector/transforms/CamelTypeConverterTransform.java
index 2078ea21fa..6241835d70 100644
--- 
a/core/src/main/java/org/apache/camel/kafkaconnector/transforms/CamelTypeConverterTransform.java
+++ 
b/core/src/main/java/org/apache/camel/kafkaconnector/transforms/CamelTypeConverterTransform.java
@@ -41,11 +41,16 @@ public abstract class CamelTypeConverterTransform<R extends 
ConnectRecord<R>> ex
 
     @Override
     public R apply(R record) {
-        final Schema schema = operatingSchema(record);
         final Object value = operatingValue(record);
+        if (value == null) {
+            // nothing to convert, for example a tombstone: pass it through 
unchanged, like Kafka's Cast SMT does
+            return record;
+        }
 
+        final Schema schema = operatingSchema(record);
         final Object convertedValue = 
convertValueWithCamelTypeConverter(value);
-        final Schema updatedSchema = getOrBuildRecordSchema(schema, 
convertedValue);
+        // a schemaless record stays schemaless
+        final Schema updatedSchema = schema == null ? null : 
getOrBuildRecordSchema(schema, convertedValue);
 
         return newRecord(record, updatedSchema, convertedValue);
     }
diff --git 
a/core/src/test/java/org/apache/camel/kafkaconnector/transforms/CamelTypeConverterTransformTest.java
 
b/core/src/test/java/org/apache/camel/kafkaconnector/transforms/CamelTypeConverterTransformTest.java
index 4cc93ff110..4e65262acd 100644
--- 
a/core/src/test/java/org/apache/camel/kafkaconnector/transforms/CamelTypeConverterTransformTest.java
+++ 
b/core/src/test/java/org/apache/camel/kafkaconnector/transforms/CamelTypeConverterTransformTest.java
@@ -41,6 +41,7 @@ import static 
org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -72,6 +73,56 @@ public class CamelTypeConverterTransformTest {
         assertEquals(Schema.BOOLEAN_SCHEMA, 
transformedValueSourceRecord.valueSchema());
     }
 
+    @Test
+    public void testIfItPassesNullValuesThrough() {
+        final Map<String, Object> props = new HashMap<>();
+        props.put(CamelTypeConverterTransform.FIELD_TARGET_TYPE_CONFIG, 
"java.lang.String");
+
+        final Transformation<SourceRecord> transformationKey = new 
CamelTypeConverterTransform.Key<>();
+        final Transformation<SourceRecord> transformationValue = new 
CamelTypeConverterTransform.Value<>();
+        transformationKey.configure(props);
+        transformationValue.configure(props);
+
+        // a tombstone, with and without a schema
+        final SourceRecord schemalessTombstone = new 
SourceRecord(Collections.emptyMap(), Collections.emptyMap(), "topic", 
Schema.STRING_SCHEMA, "1234", null, null);
+        final SourceRecord tombstoneWithSchema = new 
SourceRecord(Collections.emptyMap(), Collections.emptyMap(), "topic", 
Schema.STRING_SCHEMA, "1234", Schema.OPTIONAL_BYTES_SCHEMA, null);
+        assertSame(schemalessTombstone, 
transformationValue.apply(schemalessTombstone));
+        assertSame(tombstoneWithSchema, 
transformationValue.apply(tombstoneWithSchema));
+
+        // a record without a key
+        final SourceRecord noKey = new SourceRecord(Collections.emptyMap(), 
Collections.emptyMap(), "topic", null, null, Schema.STRING_SCHEMA, "value");
+        assertSame(noKey, transformationKey.apply(noKey));
+
+        transformationKey.close();
+        transformationValue.close();
+    }
+
+    @Test
+    public void testIfItConvertsSchemalessRecords() {
+        final SourceRecord connectRecord = new 
SourceRecord(Collections.emptyMap(), Collections.emptyMap(), "topic", null, 
"1234", null, "TRUE");
+
+        final Map<String, Object> propsForKeySmt = new HashMap<>();
+        
propsForKeySmt.put(CamelTypeConverterTransform.FIELD_TARGET_TYPE_CONFIG, 
Integer.class.getName());
+        final Map<String, Object> propsForValueSmt = new HashMap<>();
+        
propsForValueSmt.put(CamelTypeConverterTransform.FIELD_TARGET_TYPE_CONFIG, 
"java.lang.Boolean");
+
+        final Transformation<SourceRecord> transformationKey = new 
CamelTypeConverterTransform.Key<>();
+        final Transformation<SourceRecord> transformationValue = new 
CamelTypeConverterTransform.Value<>();
+        transformationKey.configure(propsForKeySmt);
+        transformationValue.configure(propsForValueSmt);
+
+        final SourceRecord transformedKeySourceRecord = 
transformationKey.apply(connectRecord);
+        final SourceRecord transformedValueSourceRecord = 
transformationValue.apply(connectRecord);
+
+        assertEquals(1234, transformedKeySourceRecord.key());
+        assertNull(transformedKeySourceRecord.keySchema());
+        assertEquals(true, transformedValueSourceRecord.value());
+        assertNull(transformedValueSourceRecord.valueSchema());
+
+        transformationKey.close();
+        transformationValue.close();
+    }
+
     @Test
     public void testIfItConvertsNettyCorrectly() {
         final String testMessage = "testMessage";
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index 0f82d198d3..659f8d37ba 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -21,6 +21,7 @@
 ** xref:contributor-guide/release-guide.adoc[Release Guide]
 * xref:reference/index.adoc[Reference]
 ** xref:reference/transformers/index.adoc[Single Message Transformers (SMT)]
+*** xref:reference/transformers/camelTypeConverter.adoc[Camel Type Converter]
 *** xref:reference/transformers/fieldsToHeaders.adoc[Fields To Headers]
 ** xref:reference/index.adoc[Connectors]
 // connectors: START
diff --git 
a/docs/modules/ROOT/pages/reference/transformers/camelTypeConverter.adoc 
b/docs/modules/ROOT/pages/reference/transformers/camelTypeConverter.adoc
new file mode 100644
index 0000000000..a7fe09da59
--- /dev/null
+++ b/docs/modules/ROOT/pages/reference/transformers/camelTypeConverter.adoc
@@ -0,0 +1,44 @@
+[[CamelTypeConverter-CamelTypeConverter]]
+= CamelTypeConverter
+The following provides usage information for the SMT 
`org.apache.camel.kafkaconnector.transforms.CamelTypeConverterTransform`
+
+== Description
+
+CamelTypeConverter converts the key or the value of a record to another Java 
type using the
+https://camel.apache.org/manual/type-converter.html[Camel type converters], 
for example a `byte[]`
+or a Camel stream into a `String`, or a Kafka Connect `Struct` into a `Map`.
+
+The schema of the converted key or value is derived from the converted value. 
Records without a
+schema (for example records read with `value.converter.schemas.enable=false`) 
stay without a schema.
+
+A null key or value, for example the value of a tombstone record, is passed 
through unchanged.
+
+Use the concrete transformation type designed for the record key
+(`org.apache.camel.kafkaconnector.transforms.CamelTypeConverterTransform$Key`) 
or value
+(`org.apache.camel.kafkaconnector.transforms.CamelTypeConverterTransform$Value`).
+
+If a key or value cannot be converted to the target type, the transformation 
fails with a
+`DataException`.
+
+== Examples
+
+=== Given the following configuration:
+
+[source,java-properties]
+----
+transforms=toString
+transforms.toString.type=org.apache.camel.kafkaconnector.transforms.CamelTypeConverterTransform$Value
+transforms.toString.target.type=java.lang.String
+----
+and a record whose value is the `byte[]` of the JSON document `{"id": 1}`, the 
value of the
+transformed record is the string `{"id": 1}` with a `STRING` schema.
+
+== Properties
+
+[cols="^,^ ", options="header"]
+|===
+|Name | Description
+
+|target.type
+|The fully qualified name of the Java class to convert the key or value to, 
for example `java.lang.String` or `java.util.Map`.
+|===
diff --git a/docs/modules/ROOT/pages/reference/transformers/index.adoc 
b/docs/modules/ROOT/pages/reference/transformers/index.adoc
index 0f96b24514..36dc07d72b 100644
--- a/docs/modules/ROOT/pages/reference/transformers/index.adoc
+++ b/docs/modules/ROOT/pages/reference/transformers/index.adoc
@@ -17,6 +17,9 @@ Camel Kafka Connector provides additional SMTs:
 |===
 |Transform | Description
 
+|xref:reference/transformers/camelTypeConverter.adoc[CamelTypeConverter]
+|Convert a kafka connect record's key or value to another Java type with the 
Camel type converters
+
 |xref:reference/transformers/fieldsToHeaders.adoc[FieldsToHeaders]
 |Extract fields from a kafka connect record's value or key and copy the value 
in configured headers (struct + schema, map or copy the whole value/key are the 
supported options)
 

Reply via email to