Repository: apache/kafka
Pull request #19467: MINOR: improve (De)Serializer JavaDocs
Author: login: mjsax
Labels: name: docs
name: clients
Created: 2025-04-14T17:02:19Z | Merged: 2025-04-17T18:23:15Z

Reviewers: Kirk True <ktrue@confluent.io>, Lianet Magrans
 <lmagrans@confluent.io>



Diff:
diff --git a/clients/src/main/java/org/apache/kafka/common/serialization/Deserializer.java b/clients/src/main/java/org/apache/kafka/common/serialization/Deserializer.java
index 1997e66aad205..d48574b54fabf 100644
--- a/clients/src/main/java/org/apache/kafka/common/serialization/Deserializer.java
+++ b/clients/src/main/java/org/apache/kafka/common/serialization/Deserializer.java
@@ -25,20 +25,25 @@
 
 /**
  * An interface for converting bytes to objects.
- *
  * A class that implements this interface is expected to have a constructor with no parameters.
- * <p>
- * Implement {@link org.apache.kafka.common.ClusterResourceListener} to receive cluster metadata once it's available. Please see the class documentation for ClusterResourceListener for more information.
- * Implement {@link org.apache.kafka.common.metrics.Monitorable} to enable the deserializer to register metrics. The following tags are automatically added to
- * all metrics registered: <code>config</code> set to either <code>key.deserializer</code> or <code>value.deserializer</code>, and <code>class</code> set to the Deserializer class name.
+ *
+ * <p>This interface can be combined with {@link org.apache.kafka.common.ClusterResourceListener ClusterResourceListener}
+ * to receive cluster metadata once it's available, as well as {@link org.apache.kafka.common.metrics.Monitorable Monitorable}
+ * to enable the deserializer to register metrics. For the latter, the following tags are automatically added to
+ * all metrics registered: {@code config} set to either {@code key.deserializer} or {@code value.deserializer},
+ * and {@code class} set to the deserializer class name.
+ *
  * @param <T> Type to be deserialized into.
  */
 public interface Deserializer<T> extends Closeable {
 
     /**
      * Configure this class.
-     * @param configs configs in key/value pairs
-     * @param isKey whether is for key or value
+     *
+     * @param configs
+     *        configs in key/value pairs
+     * @param isKey
+     *        whether the deserializer is used for the key or the value
      */
     default void configure(Map<String, ?> configs, boolean isKey) {
         // intentionally left blank
@@ -46,18 +51,35 @@ default void configure(Map<String, ?> configs, boolean isKey) {
 
     /**
      * Deserialize a record value from a byte array into a value or object.
-     * @param topic topic associated with the data
-     * @param data serialized bytes; may be null; implementations are recommended to handle null by returning a value or null rather than throwing an exception.
-     * @return deserialized typed data; may be null
+     *
+     * <p>It is recommended to deserialize a {@code null} byte array to a {@code null} object.
+     *
+     * @param topic
+     *        topic associated with the data
+     * @param data
+     *        serialized bytes; may be {@code null}
+     *
+     * @return deserialized typed data; may be {@code null}
      */
     T deserialize(String topic, byte[] data);
 
     /**
      * Deserialize a record value from a byte array into a value or object.
-     * @param topic topic associated with the data
-     * @param headers headers associated with the record; may be empty.
-     * @param data serialized bytes; may be null; implementations are recommended to handle null by returning a value or null rather than throwing an exception.
-     * @return deserialized typed data; may be null
+     *
+     * <p>It is recommended to deserialize a {@code null} byte array to a {@code null} object.
+     *
+     * <p>Note that the passed in {@link Headers} may be empty, but never {@code null}.
+     * The implementation is allowed to modify the passed in headers, as a side effect of deserialization.
+     * It is considered best practice to not delete or modify existing headers, but rather only add new ones.
+     *
+     * @param topic
+     *        topic associated with the data
+     * @param headers
+     *        headers associated with the record
+     * @param data
+     *        serialized bytes; may be {@code null}
+     *
+     * @return deserialized typed data; may be {@code null}
      */
     default T deserialize(String topic, Headers headers, byte[] data) {
         return deserialize(topic, data);
@@ -73,10 +95,20 @@ default T deserialize(String topic, Headers headers, byte[] data) {
      * <p>Similarly, if this method is overridden, the implementation cannot make any assumptions about the
      * passed in {@link ByteBuffer} either.
      *
-     * @param topic topic associated with the data
-     * @param headers headers associated with the record; may be empty.
-     * @param data serialized ByteBuffer; may be null; implementations are recommended to handle null by returning a value or null rather than throwing an exception.
-     * @return deserialized typed data; may be null
+     * <p>It is recommended to deserialize a {@code null} {@link ByteBuffer} to a {@code null} object.
+     *
+     * <p>Note that the passed in {@link Headers} may be empty, but never {@code null}.
+     * The implementation is allowed to modify the passed in headers, as a side effect of deserialization.
+     * It is considered best practice to not delete or modify existing headers, but rather only add new ones.
+     *
+     * @param topic
+     *        topic associated with the data
+     * @param headers
+     *        headers associated with the record
+     * @param data
+     *        serialized ByteBuffer; may be {@code null}
+     *
+     * @return deserialized typed data; may be {@code null}
      */
     default T deserialize(String topic, Headers headers, ByteBuffer data) {
         return deserialize(topic, headers, Utils.toNullableArray(data));
@@ -84,8 +116,8 @@ default T deserialize(String topic, Headers headers, ByteBuffer data) {
 
     /**
      * Close this deserializer.
-     * <p>
-     * This method must be idempotent as it may be called multiple times.
+     *
+     * <p>This method must be idempotent as it may be called multiple times.
      */
     @Override
     default void close() {
diff --git a/clients/src/main/java/org/apache/kafka/common/serialization/Serializer.java b/clients/src/main/java/org/apache/kafka/common/serialization/Serializer.java
index 03eab512aa71d..0730b71bcade1 100644
--- a/clients/src/main/java/org/apache/kafka/common/serialization/Serializer.java
+++ b/clients/src/main/java/org/apache/kafka/common/serialization/Serializer.java
@@ -23,20 +23,25 @@
 
 /**
  * An interface for converting objects to bytes.
- *
  * A class that implements this interface is expected to have a constructor with no parameter.
- * <p>
- * Implement {@link org.apache.kafka.common.ClusterResourceListener} to receive cluster metadata once it's available. Please see the class documentation for ClusterResourceListener for more information.
- * Implement {@link org.apache.kafka.common.metrics.Monitorable} to enable the serializer to register metrics. The following tags ae automatically added to
- * all metrics registered: <code>config</code> set to either <code>key.serializer</code> or <code>value.serializer</code>, and <code>class</code> set to the Serializer class name.
+ *
+ * <p>This interface can be combined with {@link org.apache.kafka.common.ClusterResourceListener ClusterResourceListener}
+ * to receive cluster metadata once it's available, as well as {@link org.apache.kafka.common.metrics.Monitorable Monitorable}
+ * to enable the serializer to register metrics. For the latter, the following tags are automatically added to all
+ * metrics registered: {@code config} set to either {@code key.serializer} or {@code value.serializer},
+ * and {@code class} set to the serializer class name.
+ *
  * @param <T> Type to be serialized from.
  */
 public interface Serializer<T> extends Closeable {
 
     /**
      * Configure this class.
-     * @param configs configs in key/value pairs
-     * @param isKey whether is for key or value
+     *
+     * @param configs
+     *        configs in key/value pairs
+     * @param isKey
+     *        whether the serializer is used for the key or the value
      */
     default void configure(Map<String, ?> configs, boolean isKey) {
         // intentionally left blank
@@ -45,19 +50,34 @@ default void configure(Map<String, ?> configs, boolean isKey) {
     /**
      * Convert {@code data} into a byte array.
      *
-     * @param topic topic associated with data
-     * @param data typed data
-     * @return serialized bytes
+     * <p>It is recommended to serialize {@code null} data to the {@code null} byte array.
+     *
+     * @param topic
+     *        topic associated with data
+     * @param data
+     *        typed data; may be {@code null}
+     *
+     * @return serialized bytes; may be {@code null}
      */
     byte[] serialize(String topic, T data);
 
     /**
      * Convert {@code data} into a byte array.
      *
-     * @param topic topic associated with data
-     * @param headers headers associated with the record
-     * @param data typed data
-     * @return serialized bytes
+     * <p>It is recommended to serialize {@code null} data to the {@code null} byte array.
+     *
+     * <p>Note that the passed in {@link Headers} may be empty, but never {@code null}.
+     * The implementation is allowed to modify the passed in headers, as a side effect of serialization.
+     * It is considered best practice to not delete or modify existing headers, but rather only add new ones.
+     *
+     * @param topic
+     *        topic associated with data
+     * @param headers
+     *        headers associated with the record
+     * @param data
+     *        typed data; may be {@code null}
+     *
+     * @return serialized bytes; may be {@code null}
      */
     default byte[] serialize(String topic, Headers headers, T data) {
         return serialize(topic, data);
@@ -65,8 +85,8 @@ default byte[] serialize(String topic, Headers headers, T data) {
 
     /**
      * Close this serializer.
-     * <p>
-     * This method must be idempotent as it may be called multiple times.
+     *
+     * <p>This method must be idempotent as it may be called multiple times.
      */
     @Override
     default void close() {


Reviews:
user: login: lianetm
body: Thanks! Just some nits
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T19:21:02Z
user: login: kirktrue
body: Thanks for the PR @mjsax.

Are the line breaking changes for the `@param` JavaDoc a new style guideline, or one used in Streams? I haven't seen that elsewhere in the project.

Thanks!
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T14:40:49Z
user: login: mjsax
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T17:39:08Z
user: login: kirktrue
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T16:09:39Z
user: login: lianetm
body: Thanks for the updates! LGTM.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-17T17:55:50Z

Review comments:
diff_hunk: @@ -25,39 +25,61 @@
 
 /**
  * An interface for converting bytes to objects.
- *
  * A class that implements this interface is expected to have a constructor with no parameters.
- * <p>
- * Implement {@link org.apache.kafka.common.ClusterResourceListener} to receive cluster metadata once it's available. Please see the class documentation for ClusterResourceListener for more information.
- * Implement {@link org.apache.kafka.common.metrics.Monitorable} to enable the deserializer to register metrics. The following tags are automatically added to
- * all metrics registered: <code>config</code> set to either <code>key.deserializer</code> or <code>value.deserializer</code>, and <code>class</code> set to the Deserializer class name.
+ *
+ * <p>This interface can be combined with {@link org.apache.kafka.common.ClusterResourceListener ClusterResourceListener}
+ * to receive cluster metadata once it's available, as well as {@link org.apache.kafka.common.metrics.Monitorable Monitorable}
+ * to enable the deserializer to register metrics. For the later case, the following tags are automatically added to
+ * all metrics registered: {@code config} set to either {@code key.deserializer} or {@code value.deserializer},
+ * and {@code class} set to the deserializer class name.
+ *
  * @param <T> Type to be deserialized into.
  */
 public interface Deserializer<T> extends Closeable {
 
     /**
      * Configure this class.
-     * @param configs configs in key/value pairs
-     * @param isKey whether is for key or value
+     *
+     * @param configs
+     *        configs in key/value pairs
+     * @param isKey
+     *        whether is for key or value
      */
     default void configure(Map<String, ?> configs, boolean isKey) {
         // intentionally left blank
     }
 
     /**
      * Deserialize a record value from a byte array into a value or object.
-     * @param topic topic associated with the data
-     * @param data serialized bytes; may be null; implementations are recommended to handle null by returning a value or null rather than throwing an exception.
-     * @return deserialized typed data; may be null
+     *
+     * <p>It is recommended to deserialize a {@code null} byte array to a {@code null} object.
+     *
+     * @param topic
+     *        topic associated with the data
+     * @param data
+     *        serialized bytes; may be {@code null}
+     *
+     * @return deserialized typed data; may be {@code null}
      */
     T deserialize(String topic, byte[] data);
 
     /**
      * Deserialize a record value from a byte array into a value or object.
-     * @param topic topic associated with the data
-     * @param headers headers associated with the record; may be empty.
-     * @param data serialized bytes; may be null; implementations are recommended to handle null by returning a value or null rather than throwing an exception.
-     * @return deserialized typed data; may be null
+     *
+     * <p>It is recommended to deserialize a {@code null} byte array to a {@code null} object.
+     *
+     * <p>Note that the passed in {@link Headers} may be empty, but never {@code null}.
+     * The implementation is allowed to modify the passed in headers, as a side effect of deserialization.
+     * It is considered best practise to not delete or modify existing headers, but rather only add new ones.
path: clients/src/main/java/org/apache/kafka/common/serialization/Deserializer.java
user: login: lianetm
body: ```suggestion
     * It is considered best practice to not delete or modify existing headers, but rather only add new ones.
```
created_at: 2025-04-15T19:06:05Z
author_association: MEMBER
diff_hunk: @@ -25,39 +25,61 @@
 
 /**
  * An interface for converting bytes to objects.
- *
  * A class that implements this interface is expected to have a constructor with no parameters.
- * <p>
- * Implement {@link org.apache.kafka.common.ClusterResourceListener} to receive cluster metadata once it's available. Please see the class documentation for ClusterResourceListener for more information.
- * Implement {@link org.apache.kafka.common.metrics.Monitorable} to enable the deserializer to register metrics. The following tags are automatically added to
- * all metrics registered: <code>config</code> set to either <code>key.deserializer</code> or <code>value.deserializer</code>, and <code>class</code> set to the Deserializer class name.
+ *
+ * <p>This interface can be combined with {@link org.apache.kafka.common.ClusterResourceListener ClusterResourceListener}
+ * to receive cluster metadata once it's available, as well as {@link org.apache.kafka.common.metrics.Monitorable Monitorable}
+ * to enable the deserializer to register metrics. For the later case, the following tags are automatically added to
path: clients/src/main/java/org/apache/kafka/common/serialization/Deserializer.java
user: login: lianetm
body: latter (and we could just remove 'case')
created_at: 2025-04-15T19:17:10Z
author_association: MEMBER
diff_hunk: @@ -73,19 +95,29 @@ default T deserialize(String topic, Headers headers, byte[] data) {
      * <p>Similarly, if this method is overridden, the implementation cannot make any assumptions about the
      * passed in {@link ByteBuffer} either.
      *
-     * @param topic topic associated with the data
-     * @param headers headers associated with the record; may be empty.
-     * @param data serialized ByteBuffer; may be null; implementations are recommended to handle null by returning a value or null rather than throwing an exception.
-     * @return deserialized typed data; may be null
+     * <p>It is recommended to deserialize a {@code null} {@link ByteBuffer} to a {@code null} object.
+     *
+     * <p>Note that the passed in {@link Headers} may be empty, but never {@code null}.
+     * The implementation is allowed to modify the passed in headers, as a side effect of deserialization.
+     * It is considered best practise to not delete or modify existing headers, but rather only add new ones.
path: clients/src/main/java/org/apache/kafka/common/serialization/Deserializer.java
user: login: lianetm
body: ```suggestion
     * It is considered best practice to not delete or modify existing headers, but rather only add new ones.
```
created_at: 2025-04-15T19:19:10Z
author_association: MEMBER
diff_hunk: @@ -45,28 +50,43 @@ default void configure(Map<String, ?> configs, boolean isKey) {
     /**
      * Convert {@code data} into a byte array.
      *
-     * @param topic topic associated with data
-     * @param data typed data
-     * @return serialized bytes
+     * <p>It is recommended to serialize {@code null} data to the {@code null} byte array.
+     *
+     * @param topic
+     *        topic associated with data
+     * @param data
+     *        typed data; may be {@code null}
+     *
+     * @return serialized bytes; may be {@code null}
      */
     byte[] serialize(String topic, T data);
 
     /**
      * Convert {@code data} into a byte array.
      *
-     * @param topic topic associated with data
-     * @param headers headers associated with the record
-     * @param data typed data
-     * @return serialized bytes
+     * <p>It is recommended to serialize {@code null} data to the {@code null} byte array.
+     *
+     * <p>Note that the passed in {@link Headers} may be empty, but never {@code null}.
+     * The implementation is allowed to modify the passed in headers, as a side effect of serialization.
+     * It is considered best practise to not delete or modify existing headers, but rather only add new ones.
path: clients/src/main/java/org/apache/kafka/common/serialization/Serializer.java
user: login: lianetm
body: ```suggestion
     * It is considered best practice to not delete or modify existing headers, but rather only add new ones.
```
created_at: 2025-04-15T19:19:53Z
author_association: MEMBER
diff_hunk: @@ -25,39 +25,61 @@
 
 /**
  * An interface for converting bytes to objects.
- *
  * A class that implements this interface is expected to have a constructor with no parameters.
- * <p>
- * Implement {@link org.apache.kafka.common.ClusterResourceListener} to receive cluster metadata once it's available. Please see the class documentation for ClusterResourceListener for more information.
- * Implement {@link org.apache.kafka.common.metrics.Monitorable} to enable the deserializer to register metrics. The following tags are automatically added to
- * all metrics registered: <code>config</code> set to either <code>key.deserializer</code> or <code>value.deserializer</code>, and <code>class</code> set to the Deserializer class name.
+ *
+ * <p>This interface can be combined with {@link org.apache.kafka.common.ClusterResourceListener ClusterResourceListener}
+ * to receive cluster metadata once it's available, as well as {@link org.apache.kafka.common.metrics.Monitorable Monitorable}
+ * to enable the deserializer to register metrics. For the later case, the following tags are automatically added to
+ * all metrics registered: {@code config} set to either {@code key.deserializer} or {@code value.deserializer},
+ * and {@code class} set to the deserializer class name.
+ *
  * @param <T> Type to be deserialized into.
  */
 public interface Deserializer<T> extends Closeable {
 
     /**
      * Configure this class.
-     * @param configs configs in key/value pairs
-     * @param isKey whether is for key or value
+     *
+     * @param configs
+     *        configs in key/value pairs
+     * @param isKey
+     *        whether is for key or value
path: clients/src/main/java/org/apache/kafka/common/serialization/Deserializer.java
user: login: kirktrue
body: Excuse my ignorance, but whether _what_ is for the key or value?
created_at: 2025-04-16T14:33:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23,20 +23,25 @@
 
 /**
  * An interface for converting objects to bytes.
- *
  * A class that implements this interface is expected to have a constructor with no parameter.
- * <p>
- * Implement {@link org.apache.kafka.common.ClusterResourceListener} to receive cluster metadata once it's available. Please see the class documentation for ClusterResourceListener for more information.
- * Implement {@link org.apache.kafka.common.metrics.Monitorable} to enable the serializer to register metrics. The following tags ae automatically added to
- * all metrics registered: <code>config</code> set to either <code>key.serializer</code> or <code>value.serializer</code>, and <code>class</code> set to the Serializer class name.
+ *
+ * <p>This interface can be combined with {@link org.apache.kafka.common.ClusterResourceListener ClusterResourceListener}
+ * to receive cluster metadata once it's available, as well as {@link org.apache.kafka.common.metrics.Monitorable Monitorable}
+ * to enable the serializer to register metrics. For the later case, the following tags are automatically added to all
path: clients/src/main/java/org/apache/kafka/common/serialization/Serializer.java
user: login: kirktrue
body: ```suggestion
 * to enable the serializer to register metrics. For the latter case, the following tags are automatically added to all
```
created_at: 2025-04-16T14:37:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,39 +25,61 @@
 
 /**
  * An interface for converting bytes to objects.
- *
  * A class that implements this interface is expected to have a constructor with no parameters.
- * <p>
- * Implement {@link org.apache.kafka.common.ClusterResourceListener} to receive cluster metadata once it's available. Please see the class documentation for ClusterResourceListener for more information.
- * Implement {@link org.apache.kafka.common.metrics.Monitorable} to enable the deserializer to register metrics. The following tags are automatically added to
- * all metrics registered: <code>config</code> set to either <code>key.deserializer</code> or <code>value.deserializer</code>, and <code>class</code> set to the Deserializer class name.
+ *
+ * <p>This interface can be combined with {@link org.apache.kafka.common.ClusterResourceListener ClusterResourceListener}
+ * to receive cluster metadata once it's available, as well as {@link org.apache.kafka.common.metrics.Monitorable Monitorable}
+ * to enable the deserializer to register metrics. For the later case, the following tags are automatically added to
+ * all metrics registered: {@code config} set to either {@code key.deserializer} or {@code value.deserializer},
+ * and {@code class} set to the deserializer class name.
+ *
  * @param <T> Type to be deserialized into.
  */
 public interface Deserializer<T> extends Closeable {
 
     /**
      * Configure this class.
-     * @param configs configs in key/value pairs
-     * @param isKey whether is for key or value
+     *
+     * @param configs
+     *        configs in key/value pairs
+     * @param isKey
+     *        whether is for key or value
path: clients/src/main/java/org/apache/kafka/common/serialization/Deserializer.java
user: login: mjsax
body: Good point. Did not pay close attention. It's very badly written. I'll update it.
created_at: 2025-04-16T17:39:08Z
author_association: MEMBER

PR comments:
user: login: mjsax
created_at: 2025-04-16T17:40:50Z
body: > Are the line breaking changes for the @param JavaDoc a new style guideline, or one used in Streams? I haven't seen that elsewhere in the project.

I don't think there is any official style guide, but yes, it's format which we started to use in KS (we still have lot's of JavaDocs not use this formatting yet though). -- I find it much easier to read, and also the git diffs are cleaner if modified.

Any objections to it?
author_association: MEMBER
user: login: kirktrue
created_at: 2025-04-17T16:08:19Z
body: > > Are the line breaking changes for the @param JavaDoc a new style guideline, or one used in Streams? I haven't seen that elsewhere in the project.
> 
> I don't think there is any official style guide, but yes, it's format which we started to use in KS (we still have lot's of JavaDocs not use this formatting yet though). -- I find it much easier to read, and also the git diffs are cleaner if modified.
> 
> Any objections to it?

I don't personally 😄
author_association: CONTRIBUTOR