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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new fa91d7f4463d CAMEL-23997: Fix medium-severity findings from 
camel-kafka code review
fa91d7f4463d is described below

commit fa91d7f4463d0ce2ea6f4da12ce42e69caed4f53
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jul 15 09:05:03 2026 +0200

    CAMEL-23997: Fix medium-severity findings from camel-kafka code review
    
    Fix 11 medium-severity findings across configuration, consumer, producer,
    and kamelet transforms:
    
    Configuration: sslEndpointAlgorithm=none now correctly disables hostname
    verification; offsetRepository properly disables Kafka auto-commit via
    consolidated isAutoCommitEnable(); sendBufferBytes applied to consumers
    on PLAINTEXT; getAutoCommitEnable() deprecated.
    
    Consumer: reconnect flag properly reset after successful reconnect;
    auto-generated groupId shared across all consumer threads; rebalance
    listeners migrated to isAutoCommitEnable() fixing offset-repository
    manual commit on partition revoke.
    
    Producer batch mode: plain list elements type-converted via configured
    serializer; Message keys properly converted; endpoint-configured key
    respected for batch elements; per-element header recomputation fixed;
    DefaultKafkaHeaderSerializer CamelContext injected in doStart().
    
    Kamelets: ReplaceField renames parsing uses correct guard variable;
    MessageTimestampRouter searches for first key present in payload and
    handles numeric timestamp fields.
    
    Closes #24693
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../camel/component/kafka/KafkaConfiguration.java  | 20 +++---
 .../camel/component/kafka/KafkaConsumer.java       |  7 +-
 .../camel/component/kafka/KafkaFetchRecords.java   |  2 +-
 .../camel/component/kafka/KafkaProducer.java       |  3 +
 .../support/classic/ClassicRebalanceListener.java  |  2 +-
 .../support/resume/ResumeRebalanceListener.java    |  2 +-
 .../producer/support/KeyValueHolderIterator.java   | 25 +++----
 .../kafka/transform/MessageTimestampRouter.java    |  6 +-
 .../component/kafka/transform/ReplaceField.java    |  2 +-
 .../component/kafka/KafkaConfigurationTest.java    | 83 ++++++++++++++++++++++
 .../camel/component/kafka/KafkaConsumerTest.java   | 29 ++++++++
 .../transform/MessageTimestampRouterTest.java      | 79 ++++++++++++++++++++
 .../kafka/transform/ReplaceFieldTest.java          | 14 ++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc    | 22 ++++++
 14 files changed, 265 insertions(+), 31 deletions(-)

diff --git 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaConfiguration.java
 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaConfiguration.java
index 1e0ec5d2d957..e074ec9e3ad9 100755
--- 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaConfiguration.java
+++ 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaConfiguration.java
@@ -553,6 +553,8 @@ public class KafkaConfiguration implements Cloneable, 
HeaderFilterStrategyAware
             String algo = getSslEndpointAlgorithm();
             if (algo != null && !algo.equals("none") && !algo.equals("false")) 
{
                 addPropertyIfNotNull(props, 
SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, algo);
+            } else {
+                
props.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
             }
             addPropertyIfNotEmpty(props, 
SslConfigs.SSL_KEYMANAGER_ALGORITHM_CONFIG, getSslKeymanagerAlgorithm());
             addPropertyIfNotEmpty(props, 
SslConfigs.SSL_TRUSTMANAGER_ALGORITHM_CONFIG, getSslTrustmanagerAlgorithm());
@@ -580,13 +582,14 @@ public class KafkaConfiguration implements Cloneable, 
HeaderFilterStrategyAware
         addPropertyIfNotEmpty(props, 
ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, getInterceptorClasses());
         addPropertyIfNotEmpty(props, ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, 
getAutoOffsetReset());
         addPropertyIfNotEmpty(props, 
ConsumerConfig.CONNECTIONS_MAX_IDLE_MS_CONFIG, getConnectionMaxIdleMs());
-        addPropertyIfNotEmpty(props, ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, 
getAutoCommitEnable());
+        addPropertyIfNotEmpty(props, ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, 
isAutoCommitEnable());
         if (classicProtocol) {
             addPropertyIfNotEmpty(props, 
ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, getPartitionAssignor());
         }
         addPropertyIfNotEmpty(props, ConsumerConfig.GROUP_PROTOCOL_CONFIG, 
getGroupProtocol());
         addPropertyIfNotEmpty(props, 
ConsumerConfig.GROUP_REMOTE_ASSIGNOR_CONFIG, getGroupRemoteAssignor());
         addPropertyIfNotEmpty(props, ConsumerConfig.RECEIVE_BUFFER_CONFIG, 
getReceiveBufferBytes());
+        addPropertyIfNotEmpty(props, ConsumerConfig.SEND_BUFFER_CONFIG, 
getSendBufferBytes());
         addPropertyIfNotEmpty(props, ConsumerConfig.REQUEST_TIMEOUT_MS_CONFIG, 
getConsumerRequestTimeoutMs());
         addPropertyIfNotEmpty(props, 
ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, getAutoCommitIntervalMs());
         addPropertyIfNotEmpty(props, ConsumerConfig.CHECK_CRCS_CONFIG, 
getCheckCrcs());
@@ -641,6 +644,8 @@ public class KafkaConfiguration implements Cloneable, 
HeaderFilterStrategyAware
             String algo = getSslEndpointAlgorithm();
             if (algo != null && !algo.equals("none") && !algo.equals("false")) 
{
                 addPropertyIfNotNull(props, 
SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, algo);
+            } else {
+                
props.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
             }
             addPropertyIfNotEmpty(props, 
SslConfigs.SSL_KEYMANAGER_ALGORITHM_CONFIG, getSslKeymanagerAlgorithm());
             addPropertyIfNotEmpty(props, 
SslConfigs.SSL_TRUSTMANAGER_ALGORITHM_CONFIG, getSslTrustmanagerAlgorithm());
@@ -649,7 +654,6 @@ public class KafkaConfiguration implements Cloneable, 
HeaderFilterStrategyAware
             addPropertyIfNotEmpty(props, SslConfigs.SSL_PROTOCOL_CONFIG, 
getSslProtocol());
             addPropertyIfNotEmpty(props, SslConfigs.SSL_PROVIDER_CONFIG, 
getSslProvider());
             addUpperCasePropertyIfNotEmpty(props, 
SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG, getSslTruststoreType());
-            addPropertyIfNotEmpty(props, ProducerConfig.SEND_BUFFER_CONFIG, 
getSendBufferBytes());
         }
     }
 
@@ -868,15 +872,15 @@ public class KafkaConfiguration implements Cloneable, 
HeaderFilterStrategyAware
     }
 
     public boolean isAutoCommitEnable() {
-        return offsetRepository == null && autoCommitEnable;
+        return offsetRepository == null && !batching && autoCommitEnable;
     }
 
+    /**
+     * @deprecated use {@link #isAutoCommitEnable()}
+     */
+    @Deprecated(since = "4.22.0")
     public boolean getAutoCommitEnable() {
-        if (!batching) {
-            return autoCommitEnable;
-        }
-
-        return false;
+        return isAutoCommitEnable();
     }
 
     /**
diff --git 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaConsumer.java
 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaConsumer.java
index 98f18d942c60..c1bdfe08c240 100644
--- 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaConsumer.java
+++ 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaConsumer.java
@@ -63,6 +63,7 @@ public class KafkaConsumer extends DefaultConsumer
     // This list helps to work around the infinite loop of KAFKA-1894
     private final List<KafkaFetchRecords> tasks = new ArrayList<>();
     private volatile boolean stopOffsetRepo;
+    private final String defaultGroupId = UUID.randomUUID().toString();
     private ResumeStrategy resumeStrategy;
     private KafkaConsumerListener consumerListener;
 
@@ -96,10 +97,6 @@ public class KafkaConsumer extends DefaultConsumer
         return (KafkaEndpoint) super.getEndpoint();
     }
 
-    private String randomUUID() {
-        return UUID.randomUUID().toString();
-    }
-
     Properties getProps() {
         KafkaConfiguration configuration = endpoint.getConfiguration();
 
@@ -109,7 +106,7 @@ public class KafkaConsumer extends DefaultConsumer
         
ObjectHelper.ifNotEmpty(endpoint.getKafkaClientFactory().getBrokers(configuration),
                 v -> props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, v));
 
-        String groupId = 
ObjectHelper.supplyIfEmpty(configuration.getGroupId(), this::randomUUID);
+        String groupId = configuration.getGroupId() != null ? 
configuration.getGroupId() : defaultGroupId;
         props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
 
         ObjectHelper.ifNotEmpty(configuration.getGroupInstanceId(),
diff --git 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaFetchRecords.java
 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaFetchRecords.java
index cdba2047acec..493fa245fa71 100644
--- 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaFetchRecords.java
+++ 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaFetchRecords.java
@@ -320,7 +320,7 @@ public class KafkaFetchRecords implements Runnable {
         subscribe();
 
         // set reconnect to false as the connection and resume is done at this 
point
-        setConnected(false);
+        setReconnect(false);
 
         pollExceptionStrategy.reset();
     }
diff --git 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaProducer.java
 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaProducer.java
index 92e1ab5d8e74..7c8c7d722e66 100755
--- 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaProducer.java
+++ 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaProducer.java
@@ -29,6 +29,7 @@ import java.util.concurrent.Future;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import org.apache.camel.AsyncCallback;
+import org.apache.camel.CamelContextAware;
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
 import org.apache.camel.component.kafka.producer.support.DelegatingCallback;
@@ -159,6 +160,8 @@ public class KafkaProducer extends DefaultAsyncProducer 
implements RouteIdAware
     @Override
     @SuppressWarnings("rawtypes")
     protected void doStart() throws Exception {
+        
CamelContextAware.trySetCamelContext(configuration.getHeaderSerializer(), 
getEndpoint().getCamelContext());
+
         Properties props = getProps();
         transactionId = 
ObjectHelper.isNotEmpty(configuration.getTransactionalId())
                 ? configuration.getTransactionalId() : 
props.getProperty(ProducerConfig.TRANSACTIONAL_ID_CONFIG);
diff --git 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/consumer/support/classic/ClassicRebalanceListener.java
 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/consumer/support/classic/ClassicRebalanceListener.java
index 2a8b3ca49c88..b73ef5bea83f 100644
--- 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/consumer/support/classic/ClassicRebalanceListener.java
+++ 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/consumer/support/classic/ClassicRebalanceListener.java
@@ -51,7 +51,7 @@ public class ClassicRebalanceListener implements 
ConsumerRebalanceListener {
             LOG.debug("onPartitionsRevoked: {} from {}", threadId, 
partition.topic());
 
             // only commit offsets if the component has control
-            if (!configuration.getAutoCommitEnable()) {
+            if (!configuration.isAutoCommitEnable()) {
                 commitManager.commit(partition);
             }
         }
diff --git 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/consumer/support/resume/ResumeRebalanceListener.java
 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/consumer/support/resume/ResumeRebalanceListener.java
index e8847c832387..e32d6c5a2993 100644
--- 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/consumer/support/resume/ResumeRebalanceListener.java
+++ 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/consumer/support/resume/ResumeRebalanceListener.java
@@ -52,7 +52,7 @@ public class ResumeRebalanceListener implements 
ConsumerRebalanceListener {
             LOG.debug("onPartitionsRevoked: {} from {}", threadId, 
partition.topic());
 
             // only commit offsets if the component has control
-            if (!configuration.getAutoCommitEnable()) {
+            if (!configuration.isAutoCommitEnable()) {
                 commitManager.commit(partition);
             }
         }
diff --git 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/producer/support/KeyValueHolderIterator.java
 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/producer/support/KeyValueHolderIterator.java
index 6231cf39e790..8cfb5fbbbe10 100755
--- 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/producer/support/KeyValueHolderIterator.java
+++ 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/producer/support/KeyValueHolderIterator.java
@@ -75,10 +75,16 @@ public class KeyValueHolderIterator implements 
Iterator<KeyValueHolder<Object, P
                             propagatedHeadersProvider.getHeaders(ex, 
innerMessage)));
         }
 
+        Object convertedBody = tryConvertToSerializedType(exchange, body, 
kafkaConfiguration.getValueSerializer());
+        Object key = kafkaConfiguration.getKey() != null
+                ? tryConvertToSerializedType(exchange, 
kafkaConfiguration.getKey(), kafkaConfiguration.getKeySerializer())
+                : null;
+
         return new KeyValueHolder<>(
                 body,
                 new ProducerRecord<>(
-                        msgTopic, null, null, null, body, 
propagatedHeadersProvider.getDefaultHeaders()));
+                        msgTopic, null, null, key, convertedBody,
+                        propagatedHeadersProvider.getHeaders(exchange, 
exchange.getIn())));
     }
 
     private Message getInnerMessage(Object object) {
@@ -116,19 +122,14 @@ public class KeyValueHolderIterator implements 
Iterator<KeyValueHolder<Object, P
 
     private Object getInnerKey(Exchange innerExchange, Message innerMessage) {
         Object innerKey = innerMessage.getHeader(KafkaConstants.KEY);
+        if (innerKey == null) {
+            innerKey = kafkaConfiguration.getKey();
+        }
         if (innerKey != null) {
-
-            innerKey = kafkaConfiguration.getKey() != null ? 
kafkaConfiguration.getKey() : innerKey;
-
-            if (innerKey != null) {
-                innerKey = tryConvertToSerializedType(innerExchange, innerKey,
-                        kafkaConfiguration.getKeySerializer());
-            }
-
-            return innerKey;
+            Exchange ex = innerExchange != null ? innerExchange : exchange;
+            innerKey = tryConvertToSerializedType(ex, innerKey, 
kafkaConfiguration.getKeySerializer());
         }
-
-        return null;
+        return innerKey;
     }
 
     private Integer getInnerPartitionKey(Message innerMessage) {
diff --git 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/transform/MessageTimestampRouter.java
 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/transform/MessageTimestampRouter.java
index 9009188d9a20..6225f938392d 100644
--- 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/transform/MessageTimestampRouter.java
+++ 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/transform/MessageTimestampRouter.java
@@ -59,7 +59,9 @@ public class MessageTimestampRouter {
         for (String key : splittedKeys) {
             if (ObjectHelper.isNotEmpty(key)) {
                 rawTimestamp = body.get(key);
-                break;
+                if (rawTimestamp != null) {
+                    break;
+                }
             }
         }
         Long timestamp = null;
@@ -67,7 +69,7 @@ public class MessageTimestampRouter {
                 && !timestampKeyFormat.equalsIgnoreCase("timestamp")) {
             final SimpleDateFormat timestampKeyFmt = new 
SimpleDateFormat(timestampKeyFormat);
             timestampKeyFmt.setTimeZone(TimeZone.getTimeZone("UTC"));
-            timestamp = timestampKeyFmt.parse((String) rawTimestamp).getTime();
+            timestamp = 
timestampKeyFmt.parse(rawTimestamp.toString()).getTime();
         } else if (ObjectHelper.isNotEmpty(rawTimestamp)) {
             timestamp = Long.parseLong(rawTimestamp.toString());
         }
diff --git 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/transform/ReplaceField.java
 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/transform/ReplaceField.java
index 1d225f17e0ba..c9d0499373af 100644
--- 
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/transform/ReplaceField.java
+++ 
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/transform/ReplaceField.java
@@ -46,7 +46,7 @@ public class ReplaceField {
         if (ObjectHelper.isNotEmpty(disabled) && 
!disabled.equalsIgnoreCase("none")) {
             disabledFields = 
Arrays.stream(disabled.split(",")).collect(Collectors.toList());
         }
-        if (ObjectHelper.isNotEmpty(disabled)) {
+        if (ObjectHelper.isNotEmpty(renames)) {
             renameFields = 
Arrays.stream(renames.split(",")).collect(Collectors.toList());
         }
         Map<Object, Object> updatedBody = new HashMap<>();
diff --git 
a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaConfigurationTest.java
 
b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaConfigurationTest.java
new file mode 100644
index 000000000000..0f211726799b
--- /dev/null
+++ 
b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaConfigurationTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.camel.component.kafka;
+
+import java.util.Properties;
+
+import org.apache.camel.spi.StateRepository;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.common.config.SslConfigs;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+
+class KafkaConfigurationTest {
+
+    @Test
+    void sslEndpointAlgorithmNoneDisablesHostnameVerification() {
+        KafkaConfiguration config = new KafkaConfiguration();
+        config.setBrokers("localhost:9092");
+        config.setSecurityProtocol("SSL");
+        config.setSslEndpointAlgorithm("none");
+
+        Properties props = config.createProducerProperties();
+        assertEquals("", 
props.get(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG));
+    }
+
+    @Test
+    void sslEndpointAlgorithmFalseDisablesHostnameVerification() {
+        KafkaConfiguration config = new KafkaConfiguration();
+        config.setBrokers("localhost:9092");
+        config.setSecurityProtocol("SSL");
+        config.setSslEndpointAlgorithm("false");
+
+        Properties consumerProps = config.createConsumerProperties();
+        assertEquals("", 
consumerProps.get(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG));
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    void offsetRepositoryDisablesAutoCommit() {
+        KafkaConfiguration config = new KafkaConfiguration();
+        config.setBrokers("localhost:9092");
+        config.setOffsetRepository(mock(StateRepository.class));
+
+        Properties props = config.createConsumerProperties();
+        assertEquals(false, 
props.get(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG));
+    }
+
+    @Test
+    void batchingDisablesAutoCommit() {
+        KafkaConfiguration config = new KafkaConfiguration();
+        config.setBrokers("localhost:9092");
+        config.setBatching(true);
+
+        Properties props = config.createConsumerProperties();
+        assertEquals(false, 
props.get(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG));
+    }
+
+    @Test
+    void sendBufferBytesAppliedToConsumerWithoutSsl() {
+        KafkaConfiguration config = new KafkaConfiguration();
+        config.setBrokers("localhost:9092");
+        config.setSendBufferBytes(131072);
+
+        Properties props = config.createConsumerProperties();
+        assertEquals(131072, props.get(ConsumerConfig.SEND_BUFFER_CONFIG));
+    }
+}
diff --git 
a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaConsumerTest.java
 
b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaConsumerTest.java
index 58f95e07fda4..4f10f997f8ad 100644
--- 
a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaConsumerTest.java
+++ 
b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaConsumerTest.java
@@ -16,13 +16,18 @@
  */
 package org.apache.camel.component.kafka;
 
+import java.util.Properties;
+
 import org.apache.camel.CamelContext;
 import org.apache.camel.ExtendedCamelContext;
 import org.apache.camel.Processor;
 import org.apache.camel.spi.ExchangeFactory;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
@@ -56,6 +61,30 @@ public class KafkaConsumerTest {
         assertThrows(IllegalArgumentException.class, () -> 
kafkaConsumer.getProps());
     }
 
+    @Test
+    public void autoGeneratedGroupIdIsSharedAcrossCalls() {
+        when(endpoint.getCamelContext()).thenReturn(context);
+        when(context.getCamelContextExtension()).thenReturn(ecc);
+        when(ecc.getExchangeFactory()).thenReturn(ef);
+        when(ef.newExchangeFactory(any())).thenReturn(ef);
+        when(endpoint.getComponent()).thenReturn(component);
+        when(endpoint.getConfiguration()).thenReturn(configuration);
+        when(configuration.getGroupId()).thenReturn(null);
+        when(configuration.createConsumerProperties()).thenReturn(new 
Properties());
+        when(endpoint.getKafkaClientFactory()).thenReturn(clientFactory);
+        when(clientFactory.getBrokers(any())).thenReturn("localhost:9092");
+        when(component.getKafkaClientFactory()).thenReturn(clientFactory);
+        final KafkaConsumer kafkaConsumer = new KafkaConsumer(endpoint, 
processor);
+
+        Properties props1 = kafkaConsumer.getProps();
+        Properties props2 = kafkaConsumer.getProps();
+
+        String groupId1 = (String) props1.get(ConsumerConfig.GROUP_ID_CONFIG);
+        String groupId2 = (String) props2.get(ConsumerConfig.GROUP_ID_CONFIG);
+        assertNotNull(groupId1);
+        assertEquals(groupId1, groupId2);
+    }
+
     @Test
     public void consumerOnlyRequiresBootstrapServers() {
         when(endpoint.getCamelContext()).thenReturn(context);
diff --git 
a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/transform/MessageTimestampRouterTest.java
 
b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/transform/MessageTimestampRouterTest.java
new file mode 100644
index 000000000000..5130e64df2fa
--- /dev/null
+++ 
b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/transform/MessageTimestampRouterTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.camel.component.kafka.transform;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.camel.Exchange;
+import org.apache.camel.component.kafka.KafkaConstants;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class MessageTimestampRouterTest {
+
+    private final MessageTimestampRouter router = new MessageTimestampRouter();
+    private final ObjectMapper mapper = new ObjectMapper();
+
+    @Test
+    void shouldFallbackToSecondKeyWhenFirstKeyMissing() throws Exception {
+        DefaultCamelContext camelContext = new DefaultCamelContext();
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader(KafkaConstants.TOPIC, "my-topic");
+
+        ObjectNode body = mapper.createObjectNode();
+        body.put("ts2", "1719100800000");
+        exchange.getMessage().setBody(body);
+
+        router.process("$[topic]_$[timestamp]", "yyyy-MM-dd", "ts1,ts2", 
"timestamp", exchange);
+
+        assertEquals("my-topic_2024-06-23", 
exchange.getMessage().getHeader(KafkaConstants.OVERRIDE_TOPIC));
+    }
+
+    @Test
+    void shouldHandleNumericTimestampField() throws Exception {
+        DefaultCamelContext camelContext = new DefaultCamelContext();
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader(KafkaConstants.TOPIC, "my-topic");
+
+        ObjectNode body = mapper.createObjectNode();
+        body.put("ts", 1719100800000L);
+        exchange.getMessage().setBody(body);
+
+        router.process("$[topic]_$[timestamp]", "yyyy-MM-dd", "ts", 
"timestamp", exchange);
+
+        assertEquals("my-topic_2024-06-23", 
exchange.getMessage().getHeader(KafkaConstants.OVERRIDE_TOPIC));
+    }
+
+    @Test
+    void shouldNotSetTopicWhenNoTimestampKeyMatches() throws Exception {
+        DefaultCamelContext camelContext = new DefaultCamelContext();
+        Exchange exchange = new DefaultExchange(camelContext);
+        exchange.getMessage().setHeader(KafkaConstants.TOPIC, "my-topic");
+
+        ObjectNode body = mapper.createObjectNode();
+        body.put("other", "value");
+        exchange.getMessage().setBody(body);
+
+        router.process("$[topic]_$[timestamp]", "yyyy-MM-dd", "ts1,ts2", 
"timestamp", exchange);
+
+        
assertNull(exchange.getMessage().getHeader(KafkaConstants.OVERRIDE_TOPIC));
+    }
+}
diff --git 
a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/transform/ReplaceFieldTest.java
 
b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/transform/ReplaceFieldTest.java
index b42aceafd99d..1ad133b84dc0 100644
--- 
a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/transform/ReplaceFieldTest.java
+++ 
b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/transform/ReplaceFieldTest.java
@@ -100,6 +100,20 @@ class ReplaceFieldTest {
                                                  "}");
     }
 
+    @Test
+    void shouldRenameFieldsWithoutDisabledSet() throws Exception {
+        Exchange exchange = new DefaultExchange(camelContext);
+
+        exchange.getMessage().setBody(mapper.readTree(baseJson));
+
+        JsonNode node = processor.process("all", null, 
"name:firstName,age:years", exchange);
+
+        Assertions.assertEquals(node.toString(), "{" +
+                                                 "\"firstName\":\"Rajesh 
Koothrappali\"," +
+                                                 "\"years\":\"29\"" +
+                                                 "}");
+    }
+
     @Test
     void shouldReplaceFieldWithDisableAllFields() throws Exception {
         Exchange exchange = new DefaultExchange(camelContext);
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index 935ef84e5273..ab991634201e 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -267,6 +267,28 @@ Scalar value nodes are now sent as a single record. Only 
container nodes (`Array
 are still split, which is unchanged. Any `convertBodyTo(...)` previously used 
as a workaround is no
 longer required.
 
+=== camel-kafka - sslEndpointAlgorithm=none now disables hostname verification
+
+Setting `sslEndpointAlgorithm` to `none` or `false` now correctly disables SSL 
hostname verification
+by setting `ssl.endpoint.identification.algorithm` to an empty string. 
Previously the property was
+simply omitted, which caused Kafka clients to fall back to their default 
(`https`), leaving hostname
+verification silently enabled.
+
+=== camel-kafka - Auto-generated groupId shared across consumer threads
+
+When no `groupId` is configured, the auto-generated UUID is now shared across 
all consumer threads
+(`consumersCount`). Previously each thread received its own random UUID, 
causing each thread to
+independently consume all partitions, which resulted in every message being 
processed `consumersCount`
+times.
+
+=== camel-kafka - Batch producer respects endpoint key for plain list elements
+
+The batch producer (when the body is a `List`) now respects the 
endpoint-configured `key` option for
+elements that do not have a `CamelKafkaKey` header. Previously, elements 
without the header would
+get a null record key even when `key=fixedKey` was configured on the endpoint. 
Additionally, plain
+list elements (not `Exchange` or `Message`) are now properly converted to the 
configured serializer
+type.
+
 === camel-jbang catalog tables fill the terminal width
 
 The `camel catalog` commands (`camel catalog component`, `camel catalog 
dataformat`,

Reply via email to