<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: KafmaKarma</title>
    <description>The latest articles on DEV Community by KafmaKarma (@kafmakarma).</description>
    <link>https://dev.to/kafmakarma</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4053131%2Fc39eb328-3d8a-4634-bbf2-1437aae0e320.png</url>
      <title>DEV Community: KafmaKarma</title>
      <link>https://dev.to/kafmakarma</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kafmakarma"/>
    <language>en</language>
    <item>
      <title>confluent-kafka-go in 2026: What Still Works and What to Watch</title>
      <dc:creator>KafmaKarma</dc:creator>
      <pubDate>Wed, 21 Oct 2026 10:01:38 +0000</pubDate>
      <link>https://dev.to/getkafma/confluent-kafka-go-in-2026-what-still-works-and-what-to-watch-g3j</link>
      <guid>https://dev.to/getkafma/confluent-kafka-go-in-2026-what-still-works-and-what-to-watch-g3j</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/confluentinc/confluent-kafka-go" rel="noopener noreferrer"&gt;confluent-kafka-go&lt;/a&gt; is Confluent's Kafka client for Go. It wraps librdkafka, Confluent's C client, through cgo, and it is still actively developed. v2.16.0 shipped on 7 October 2026. If you run a Go service on it, five things deserve a check: how it builds, what its Schema Registry packages pull in, where keyed records land, when offsets get committed, and which consumer group protocol it uses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; confluent-kafka-go covers transactions, admin operations, KIP-848 consumer groups, and Confluent's own Schema Registry serializers. It needs cgo, and Go turns cgo off when you cross-compile. The Schema Registry serializers need cgo too, even next to a pure Go client. The default partitioner does not match the Java client, but &lt;code&gt;murmur2_random&lt;/code&gt; does. By default, offsets can be committed before your code finishes processing a message. The consumer group protocol is expected to default to KIP-848 in a later release, so set it explicitly.&lt;/p&gt;

&lt;p&gt;For how confluent-kafka-go compares with franz-go, kafka-go, and Sarama on cgo, consumer groups, and Schema Registry, see &lt;a href="https://kafma.app/blog/kafka-golang-clients?utm_source=dev.to"&gt;Kafka Go Clients: franz-go vs kafka-go vs Sarama&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What still works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;librdkafka underneath.&lt;/strong&gt; Most protocol behavior comes from the bundled librdkafka, the same C library behind Confluent's Python, .NET, and JavaScript clients. v2.16.0 bundles librdkafka 2.16.0, with idempotent and transactional producers and an &lt;code&gt;AdminClient&lt;/code&gt; for topics, configs, ACLs, and consumer groups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No separate install on common platforms.&lt;/strong&gt; The module ships prebuilt librdkafka for macOS (x64 and arm64), glibc Linux (x64 and arm64), musl Linux such as Alpine (x64 and arm64), and Windows x64. On those platforms, you do not install librdkafka on the build or target machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;KIP-848 consumer groups.&lt;/strong&gt; The next-generation consumer group protocol, in which the broker computes partition assignments, has been production-ready since v2.12.0 on Kafka 4.0+ brokers. You turn it on with &lt;code&gt;group.protocol=consumer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confluent's Schema Registry serializers.&lt;/strong&gt; The &lt;code&gt;schemaregistry&lt;/code&gt; packages include serializers for Avro, Protobuf, and JSON Schema that register and cache schemas and write Confluent's wire format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active development.&lt;/strong&gt; In the 90 days to 25 September 2026, the repository had 55 commits and 29 merged pull requests. Over the preceding year, merges were spread across 17 accounts, and the most active one handled 39% of them. The open backlog is older, with 74 open pull requests at a median age of 2.4 years. These figures come from the &lt;a href="https://kafma.app/blog/kafka-golang-clients?utm_source=dev.to#github-repository-metrics"&gt;GitHub snapshot&lt;/a&gt; in the full comparison.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to watch
&lt;/h2&gt;

&lt;h3&gt;
  
  
  cgo is required, and cross-compiling turns it off
&lt;/h3&gt;

&lt;p&gt;The client calls librdkafka through cgo, so it builds only with &lt;code&gt;CGO_ENABLED=1&lt;/code&gt; and a C compiler. Go turns cgo off by default when you cross-compile, for example &lt;code&gt;GOOS=linux go build&lt;/code&gt; on a Mac, and when it cannot find a C compiler, as in the &lt;code&gt;golang:alpine&lt;/code&gt; image. With cgo off, a v2.16.0 build fails inside the library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# github.com/confluentinc/confluent-kafka-go/v2/kafka
.../kafka/deserialized_message.go:28:17: undefined: TopicPartition
.../kafka/deserialized_message.go:34:17: undefined: TimestampType
.../kafka/deserialized_message.go:36:19: undefined: Header
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Up to v2.15.1, the same build fails in your own code instead, with errors such as &lt;code&gt;undefined: kafka.NewProducer&lt;/code&gt; and &lt;code&gt;undefined: kafka.ConfigMap&lt;/code&gt;. To build for another platform, set &lt;code&gt;CGO_ENABLED=1&lt;/code&gt; and give cgo a C compiler for that platform, or build on the target platform itself, for example in a container for that architecture.&lt;/p&gt;

&lt;p&gt;A few more build details matter once the service ships in a container:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alpine needs &lt;code&gt;-tags musl&lt;/code&gt;.&lt;/strong&gt; The project README requires it for &lt;code&gt;go get&lt;/code&gt;, &lt;code&gt;go build&lt;/code&gt;, and similar commands on musl systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The binary is not static.&lt;/strong&gt; It links the system C library dynamically, so the runtime image needs one. &lt;code&gt;scratch&lt;/code&gt; and &lt;code&gt;distroless/static&lt;/code&gt; have none, and a binary built against a newer glibc fails on an older one. For a fully static binary, the project README describes a musl build with external-linker flags.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kerberos needs a system librdkafka.&lt;/strong&gt; The bundled builds exclude GSSAPI. Install a librdkafka with GSSAPI on both the build and target systems, and build with &lt;code&gt;-tags dynamic&lt;/code&gt;. Without librdkafka and pkg-config on the build machine, that build stops with &lt;code&gt;exec: "pkg-config": executable file not found in $PATH&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v2.16.0 needs Go 1.26.&lt;/strong&gt; The module's &lt;code&gt;go&lt;/code&gt; directive moved from 1.25.0 to 1.26.0. With the default &lt;code&gt;GOTOOLCHAIN=auto&lt;/code&gt;, Go 1.25 downloads a newer toolchain by itself. With &lt;code&gt;GOTOOLCHAIN=local&lt;/code&gt;, you need to upgrade Go first.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Schema Registry serializers need cgo too
&lt;/h3&gt;

&lt;p&gt;The Schema Registry client and serializers do not call librdkafka, so it seems natural to pair them with a pure Go client such as franz-go and build with &lt;code&gt;CGO_ENABLED=0&lt;/code&gt;. For the serializers, that stopped working in v2.10.1 (June 2025). In that release, the serializer interface gained methods that can carry the schema ID in Kafka headers. Those methods use the &lt;code&gt;kafka.Header&lt;/code&gt; type, and the &lt;code&gt;kafka&lt;/code&gt; package is the one that needs cgo.&lt;/p&gt;

&lt;p&gt;We built a small program that uses the Avro serializer (&lt;code&gt;avrov2&lt;/code&gt;) with franz-go, and a second one that uses only the Registry client, with &lt;code&gt;CGO_ENABLED=0&lt;/code&gt; and Go 1.27.1 against each release:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Release&lt;/th&gt;
&lt;th&gt;Registry client only&lt;/th&gt;
&lt;th&gt;Avro serializer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;v2.5.4 to v2.10.0&lt;/td&gt;
&lt;td&gt;Builds&lt;/td&gt;
&lt;td&gt;Builds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2.10.1 to v2.13.0&lt;/td&gt;
&lt;td&gt;Builds&lt;/td&gt;
&lt;td&gt;&lt;code&gt;undefined: kafka.Header&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2.13.2 to v2.15.1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;undefined: kafka.LibraryVersion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2.16.0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;undefined: TopicPartition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same error&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The v2.16.0 release notes say this dependency was removed from the Registry client, and that change did ship. The same release, however, added an experimental &lt;code&gt;SerializingProducer&lt;/code&gt; and &lt;code&gt;DeserializingConsumer&lt;/code&gt; API. It brought a new import of the &lt;code&gt;kafka&lt;/code&gt; package into the Registry client, and new files in the &lt;code&gt;kafka&lt;/code&gt; package that do not compile without cgo.&lt;/p&gt;

&lt;p&gt;Both problems are tracked in &lt;a href="https://github.com/confluentinc/confluent-kafka-go/issues/1635" rel="noopener noreferrer"&gt;issue #1635&lt;/a&gt;, with a proposed fix in &lt;a href="https://github.com/confluentinc/confluent-kafka-go/pull/1634" rel="noopener noreferrer"&gt;pull request #1634&lt;/a&gt;. The release notes also mark that new API as subject to change or removal, so pin the module version if you adopt it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The default partitioner does not match Java
&lt;/h3&gt;

&lt;p&gt;The default partitioner, &lt;code&gt;consistent_random&lt;/code&gt;, hashes keys with CRC32, while the Java client and franz-go use Murmur2. If a Go service and a Java service write the same keys to one topic, a key can land on two partitions, which breaks per-key ordering. Setting &lt;code&gt;partitioner&lt;/code&gt; to &lt;code&gt;murmur2_random&lt;/code&gt; gives Java-compatible placement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;kafka&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewProducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;kafka&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConfigMap&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"bootstrap.servers"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"localhost:9092"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"partitioner"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;"murmur2_random"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We checked it by producing with v2.16.0 to a local Kafka 4.3.1 broker and comparing the partition in each delivery report with the Java client 4.3.1 (&lt;code&gt;BuiltInPartitioner.partitionForKey&lt;/code&gt;). We used the keys &lt;code&gt;customer_42&lt;/code&gt;, &lt;code&gt;order-1001&lt;/code&gt;, &lt;code&gt;user-7&lt;/code&gt;, &lt;code&gt;21&lt;/code&gt;, &lt;code&gt;foobar&lt;/code&gt;, &lt;code&gt;a-little-bit-long-string&lt;/code&gt;, &lt;code&gt;café&lt;/code&gt;, and an empty key, each at 3, 6, and 12 partitions.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;murmur2_random&lt;/code&gt; matched Java in all 24 cases. The default matched in only 1 of the 21 cases with a non-empty key. For example, on a three-partition topic &lt;code&gt;customer_42&lt;/code&gt; goes to partition 1 by default and to partition 0 with &lt;code&gt;murmur2_random&lt;/code&gt;, as in Java.&lt;/p&gt;

&lt;p&gt;Empty keys behave differently too. The default sends an empty key to a random partition. Five sends to a 12-partition topic landed on partitions 4, 0, 9, 7, and 0. &lt;code&gt;murmur2_random&lt;/code&gt; hashes it as Java does and sent it to partition 9 every time. Records with a null key go to a random partition under both settings.&lt;/p&gt;

&lt;p&gt;After a switch, new records for a key can go to a different partition than its earlier records. Switch when other producers write the same keys to the same topic, and compare placements with your own keys first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Offsets can be committed before processing finishes
&lt;/h3&gt;

&lt;p&gt;By default, the consumer stores a message's offset as soon as &lt;code&gt;Poll&lt;/code&gt; or &lt;code&gt;ReadMessage&lt;/code&gt; hands the message to your code (&lt;code&gt;enable.auto.offset.store=true&lt;/code&gt;). It then commits the stored offsets in the background every 5 seconds (&lt;code&gt;enable.auto.commit=true&lt;/code&gt;, &lt;code&gt;auto.commit.interval.ms=5000&lt;/code&gt;). If the process crashes after a commit but before your code finishes a message, that message is not delivered again.&lt;/p&gt;

&lt;p&gt;For at-least-once processing, turn off the automatic store and store each offset yourself after the work is done. Auto-commit stays on and commits only what you stored:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;kafka&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;kafka&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConfigMap&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"bootstrap.servers"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;"localhost:9092"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"group.id"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;                 &lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"enable.auto.offset.store"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c"&gt;// After processing msg:&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StoreMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// handle the error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works when you process each partition's messages in order. If you process one partition's messages in parallel, store an offset only after every earlier message in that partition has succeeded. After a crash, messages that finished but were not yet committed are delivered again, so processing must tolerate duplicates.&lt;/p&gt;

&lt;h3&gt;
  
  
  KIP-848 is opt-in for now
&lt;/h3&gt;

&lt;p&gt;The default &lt;code&gt;group.protocol&lt;/code&gt; is still &lt;code&gt;classic&lt;/code&gt;, but librdkafka's &lt;a href="https://github.com/confluentinc/librdkafka/blob/v2.16.0/CONFIGURATION.md" rel="noopener noreferrer"&gt;configuration reference&lt;/a&gt; says the default "will change to &lt;code&gt;consumer&lt;/code&gt; in next releases."&lt;/p&gt;

&lt;p&gt;The new protocol changes a few contracts. The client no longer uses &lt;code&gt;partition.assignment.strategy&lt;/code&gt;, &lt;code&gt;session.timeout.ms&lt;/code&gt;, or &lt;code&gt;heartbeat.interval.ms&lt;/code&gt;. Rebalance callbacks receive only the partitions being added or revoked. If your callbacks manage assignments themselves, use &lt;code&gt;IncrementalAssign&lt;/code&gt; and &lt;code&gt;IncrementalUnassign&lt;/code&gt; instead of &lt;code&gt;Assign&lt;/code&gt; and &lt;code&gt;Unassign&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;group.protocol&lt;/code&gt; explicitly, so that a future upgrade does not switch protocols for you. Before moving a group to &lt;code&gt;consumer&lt;/code&gt;, read Confluent's &lt;a href="https://github.com/confluentinc/confluent-kafka-go/blob/v2.16.0/kafka/kip-848-migration-guide.md" rel="noopener noreferrer"&gt;KIP-848 migration guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you need what it lacks
&lt;/h2&gt;

&lt;p&gt;A service that builds and runs well on confluent-kafka-go can keep running it. If cgo is the problem, franz-go is a pure Go client that supports KIP-848. Its own Schema Registry package, &lt;code&gt;pkg/sr&lt;/code&gt;, writes the same wire format, but you supply the Avro, Protobuf, or JSON Schema codec. The full comparison linked above covers what to test before switching, including how to keep key placement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check what your service actually does
&lt;/h2&gt;

&lt;p&gt;Whether you change the partitioner, the offset store, or the build image, check the records themselves. &lt;a href="https://kafma.app/?utm_source=dev.to"&gt;Kafma, a Kafka GUI client&lt;/a&gt;, shows each record's key, partition, headers, and raw bytes, and a consumer group's committed offsets and lag. That lets you confirm that keys land where you expect. To check whether commits run ahead of processing, compare the committed offsets with your application logs. Rebalances, retries, and crash recovery still need tests in your application.&lt;/p&gt;

&lt;p&gt;Written by the team behind Kafma.&lt;/p&gt;

</description>
      <category>go</category>
      <category>kafka</category>
      <category>backend</category>
      <category>docker</category>
    </item>
    <item>
      <title>KafkaJS in 2026: What Still Works and What to Watch</title>
      <dc:creator>KafmaKarma</dc:creator>
      <pubDate>Wed, 14 Oct 2026 09:19:07 +0000</pubDate>
      <link>https://dev.to/getkafma/kafkajs-in-2026-what-still-works-and-what-to-watch-k00</link>
      <guid>https://dev.to/getkafma/kafkajs-in-2026-what-still-works-and-what-to-watch-k00</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/tulios/kafkajs" rel="noopener noreferrer"&gt;KafkaJS&lt;/a&gt; is still a widely used Kafka client for Node.js. npm counted about 3.6 million downloads in the week of 6–12 October 2026, and in August 2026 GitHub listed more than 32,000 public repositories depending on it. Its latest release, 2.2.4, shipped in February 2023. If you run KafkaJS today, or are about to inherit a service that does, here is what still works, what to watch, and how to stop the idle timer loop that newer Node.js versions now warn about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; In our test against a local Kafka 4.3.1 broker, KafkaJS 2.2.4 still produced, consumed, and ran Admin calls. It has had no release since February 2023 and does not support KIP-848 or GSSAPI. It also keeps a 1 ms timer running while a connection is idle, on every Node.js version we tested; Node.js 23 and later expose it through a warning. A small patch stops the timer.&lt;/p&gt;

&lt;p&gt;For how KafkaJS compares with Platformatic Kafka, confluent-kafka-javascript, and node-rdkafka on Node.js support, consumer models, and Schema Registry, see &lt;a href="https://kafma.app/blog/kafka-javascript-clients?utm_source=dev.to"&gt;Kafka JavaScript &amp;amp; Node.js Clients: KafkaJS vs Platformatic&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What still works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No native addon.&lt;/strong&gt; KafkaJS is pure JavaScript and declares support for Node.js 14+, so there is no librdkafka binary to build or ship for each platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A familiar Promise API.&lt;/strong&gt; Producers, consumers with &lt;code&gt;eachMessage&lt;/code&gt; and &lt;code&gt;eachBatch&lt;/code&gt; handlers, transactions, headers, and an Admin client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Common authentication.&lt;/strong&gt; PLAIN, SCRAM, and OAUTHBEARER. For Amazon MSK IAM, AWS's &lt;a href="https://github.com/aws/aws-msk-iam-sasl-signer-js" rel="noopener noreferrer"&gt;aws-msk-iam-sasl-signer-js&lt;/a&gt; README shows KafkaJS using the signer as an OAUTHBEARER token provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kafka 4.x brokers.&lt;/strong&gt; Against a local Kafka 4.3.1 broker, KafkaJS 2.2.4 created a topic, produced records, consumed them in a consumer group, and deleted the topic and group without errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to watch
&lt;/h2&gt;

&lt;h3&gt;
  
  
  No releases since February 2023
&lt;/h3&gt;

&lt;p&gt;The latest npm release is 2.2.4, published on 27 February 2023, and the last commit on the default branch was on 16 May 2024. As of 28 August 2026, the repository had 355 open issues and 62 open pull requests, and no pull request had been merged in the previous 90 days. Fixes proposed as pull requests, including the two below, are not being merged or released.&lt;/p&gt;

&lt;h3&gt;
  
  
  An idle 1 ms timer behind TimeoutNegativeWarning
&lt;/h3&gt;

&lt;p&gt;On Node.js 23 and later, including the Node.js 24 LTS line, connecting a KafkaJS producer, consumer, or admin client prints a warning like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(node:95450) TimeoutNegativeWarning: -1791964353628 is a negative number.
Timeout duration was set to 1.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The warning is new, but the behavior behind it is not. After a connection receives its first response, KafkaJS's request queue schedules a check for pending requests. When the connection is not throttled, the delay it computes is negative (&lt;code&gt;-1 - Date.now()&lt;/code&gt; by default), and Node.js runs a timer with a negative delay after 1 ms. Each check schedules the next one, so the loop keeps running while the connection is idle. The loop runs on Node.js 20 and 22 as well; of the versions we tested (20, 22, 23, 24, and 26), only 23 and later printed the warning. The report is &lt;a href="https://github.com/tulios/kafkajs/issues/1751" rel="noopener noreferrer"&gt;issue #1751&lt;/a&gt;, open since May 2025.&lt;/p&gt;

&lt;p&gt;The cost is small but constant. We ran the script below three times per configuration on an Apple Silicon Mac, with one idle, connected producer and a single-node Kafka 4.3.1 broker. It counts timers created with a delay below 1 ms and the process's CPU time over 10 seconds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Node.js&lt;/th&gt;
&lt;th&gt;KafkaJS 2.2.4&lt;/th&gt;
&lt;th&gt;With the patch below&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;22.20.0&lt;/td&gt;
&lt;td&gt;about 8,760 short timers, 253–303 ms CPU&lt;/td&gt;
&lt;td&gt;0 short timers, 43–44 ms CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24.10.0&lt;/td&gt;
&lt;td&gt;about 8,760 short timers, 202–218 ms CPU&lt;/td&gt;
&lt;td&gt;0 short timers, 14–15 ms CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is roughly 2–3% of one core for a process that is doing nothing.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Reproduction script
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// node repro.js localhost:9092   (kafkajs@2.2.4, a test broker running)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Kafka&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logLevel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kafkajs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Count timers created with a delay below 1 ms; Node.js runs them after 1 ms&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;shortTimers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;realSetTimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTimeout&lt;/span&gt;
&lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;shortTimers&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;realSetTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;kafka&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Kafka&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;brokers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost:9092&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;logLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;logLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOTHING&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;producer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;kafka&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// Measure 10 seconds of an idle, connected producer&lt;/span&gt;
  &lt;span class="nx"&gt;shortTimers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpuUsage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;realSetTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;system&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpuUsage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;shortTimers&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; short timers, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; ms CPU in 10 s`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Suppressing the warning or staying on Node.js 22 hides the message but keeps the loop. The two open pull requests for this issue, &lt;a href="https://github.com/tulios/kafkajs/pull/1755" rel="noopener noreferrer"&gt;#1755&lt;/a&gt; and &lt;a href="https://github.com/tulios/kafkajs/pull/1768" rel="noopener noreferrer"&gt;#1768&lt;/a&gt;, clamp the delay to 1 ms or 0 ms. In our test, both removed the warning, but the timer kept firing at the same rate.&lt;/p&gt;

&lt;p&gt;A patch posted in the issue thread stops the loop instead: when no request is pending and the connection is not throttled, it skips scheduling the check. In &lt;code&gt;node_modules/kafkajs/src/network/requestQueue/index.js&lt;/code&gt;, &lt;code&gt;scheduleCheckPendingRequests()&lt;/code&gt; becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;scheduleCheckPendingRequests&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;scheduleAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;throttledUntil&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;throttleCheckTimeoutId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;scheduleAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scheduleAt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;scheduleAt&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CHECK_PENDING_REQUESTS_INTERVAL&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scheduleAt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Nothing is pending and the connection is not throttled&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;throttleCheckTimeoutId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;throttleCheckTimeoutId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkPendingRequests&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;scheduleAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To keep the change across installs with npm, use &lt;a href="https://github.com/ds300/patch-package" rel="noopener noreferrer"&gt;patch-package&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;npm install patch-package&lt;/code&gt;. Keep it a regular dependency if production installs run &lt;code&gt;npm install&lt;/code&gt; or &lt;code&gt;npm ci&lt;/code&gt;, because the &lt;code&gt;postinstall&lt;/code&gt; script runs there too.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;"postinstall": "patch-package"&lt;/code&gt; to your &lt;code&gt;package.json&lt;/code&gt; scripts. If you already have a &lt;code&gt;postinstall&lt;/code&gt; script, append &lt;code&gt;&amp;amp;&amp;amp; patch-package&lt;/code&gt; to it.&lt;/li&gt;
&lt;li&gt;Edit the file above, then run &lt;code&gt;npx patch-package kafkajs&lt;/code&gt;. This writes &lt;code&gt;patches/kafkajs+2.2.4.patch&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Commit the &lt;code&gt;patches&lt;/code&gt; directory.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;pnpm and Yarn 2+ have built-in equivalents, &lt;a href="https://pnpm.io/cli/patch" rel="noopener noreferrer"&gt;&lt;code&gt;pnpm patch&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://yarnpkg.com/cli/patch" rel="noopener noreferrer"&gt;&lt;code&gt;yarn patch&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Skipping the check is safe for queued requests because &lt;code&gt;push()&lt;/code&gt; calls &lt;code&gt;scheduleCheckPendingRequests()&lt;/code&gt; again whenever a request has to wait. With the patch, a producer and a consumer group still produced and consumed records against the same broker. It is still an unofficial change to a dependency, so test it under your own load, including broker throttling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Classic consumer groups only
&lt;/h3&gt;

&lt;p&gt;KafkaJS implements the classic consumer group protocol with eager rebalancing only. When the group rebalances, each member stops fetching from all of its partitions and rejoins, and consumption resumes only after the new assignment arrives, so every rebalance pauses the whole group (stop-the-world). Cooperative rebalancing (KIP-429), which lets members keep consuming partitions that are not moving, needs support in the client's rebalance handling, not only in the assigner. A custom assigner can change which member gets which partition, but it cannot remove the pause. KafkaJS also does not support KIP-848, the broker-driven protocol that became generally available in Kafka 4.0. Kafka 4.x brokers still support the classic protocol.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication and compression gaps
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No GSSAPI/Kerberos.&lt;/strong&gt; If your cluster requires Kerberos, KafkaJS cannot connect to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only GZIP is built in.&lt;/strong&gt; Snappy, LZ4, and ZSTD come from separate codec packages, &lt;a href="https://www.npmjs.com/package/kafkajs-snappy" rel="noopener noreferrer"&gt;&lt;code&gt;kafkajs-snappy&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://www.npmjs.com/package/kafkajs-lz4" rel="noopener noreferrer"&gt;&lt;code&gt;kafkajs-lz4&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://www.npmjs.com/package/@kafkajs/zstd" rel="noopener noreferrer"&gt;&lt;code&gt;@kafkajs/zstd&lt;/code&gt;&lt;/a&gt;, last published in 2018, 2020, and 2021. Register the codec in every process that produces or consumes those records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema Registry is a separate package.&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/@kafkajs/confluent-schema-registry" rel="noopener noreferrer"&gt;&lt;code&gt;@kafkajs/confluent-schema-registry&lt;/code&gt;&lt;/a&gt; is still released (4.1.0 in May 2026), and its 4.x line requires Node.js 22+.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If you need what KafkaJS lacks
&lt;/h2&gt;

&lt;p&gt;A stable service that does not need these features can keep running KafkaJS. If you need KIP-848, Kerberos, or active maintenance, two clients publish KafkaJS migration guides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;confluent-kafka-javascript&lt;/strong&gt; keeps &lt;code&gt;eachMessage&lt;/code&gt; and &lt;code&gt;eachBatch&lt;/code&gt; through a KafkaJS-compatible API and is built on librdkafka, so it supports KIP-848. It adds a native addon to your deployment. For GSSAPI, the maintainers &lt;a href="https://github.com/confluentinc/confluent-kafka-javascript/issues/139" rel="noopener noreferrer"&gt;note&lt;/a&gt; that you need a librdkafka build with GSSAPI support, which also depends on system libraries, so verify it in your target environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platformatic Kafka&lt;/strong&gt; stays pure JavaScript and supports KIP-848. It requires Node.js 22.22+ or 24.6+, and consumers move to Node.js streams.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For what changes in each case, see the &lt;a href="https://kafma.app/blog/kafka-javascript-clients?utm_source=dev.to#migrating-from-kafkajs"&gt;migration paths&lt;/a&gt; in the full comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check what your service actually does
&lt;/h2&gt;

&lt;p&gt;Whether you patch KafkaJS or replace it, compare before and after. A &lt;a href="https://kafma.app/?utm_source=dev.to"&gt;Kafka desktop client&lt;/a&gt; such as Kafma shows each record's key, headers, partition, and raw bytes, and a consumer group's committed offsets and lag, which helps you spot changes in what your service writes and how its offsets move. Rebalances, retries, and crash recovery still need tests in your application.&lt;/p&gt;

&lt;p&gt;Written by the team behind Kafma.&lt;/p&gt;

</description>
      <category>node</category>
      <category>kafka</category>
      <category>javascript</category>
    </item>
    <item>
      <title>kafkactl: kubectl-Style Commands for Kafka</title>
      <dc:creator>KafmaKarma</dc:creator>
      <pubDate>Sat, 10 Oct 2026 06:58:58 +0000</pubDate>
      <link>https://dev.to/getkafma/kafkactl-kubectl-style-commands-for-kafka-dgg</link>
      <guid>https://dev.to/getkafma/kafkactl-kubectl-style-commands-for-kafka-dgg</guid>
      <description>&lt;p&gt;If you already think in &lt;code&gt;kubectl get&lt;/code&gt; and &lt;code&gt;kubectl config use-context&lt;/code&gt;, &lt;a href="https://github.com/deviceinsight/kafkactl" rel="noopener noreferrer"&gt;kafkactl&lt;/a&gt; will feel familiar. It is a single binary that covers producing, consuming, topics, consumer groups and offset resets, and ACLs, with the same verb-first command style.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Choose kafkactl if you want one maintained binary for records and administration, especially if your team already works in Kubernetes. Look at &lt;a href="https://github.com/twmb/kcl" rel="noopener noreferrer"&gt;kcl&lt;/a&gt;, a pure-Go CLI from the author of franz-go, instead if AWS MSK IAM without a plugin or Protobuf decoding through Schema Registry is a daily requirement.&lt;/p&gt;

&lt;p&gt;For how kafkactl compares with kcat, kaf, kcl, rpk, and Confluent CLI across authentication, Schema Registry formats, and dated GitHub activity, see &lt;a href="https://kafma.app/blog/kafka-cli-tools" rel="noopener noreferrer"&gt;Best Kafka CLI Tools in 2026: kcat vs kafkactl vs kaf&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;kafkactl installs through Homebrew, winget, Docker, or a release binary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;kafkactl      &lt;span class="c"&gt;# Homebrew&lt;/span&gt;
winget &lt;span class="nb"&gt;install &lt;/span&gt;kafkactl    &lt;span class="c"&gt;# Windows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure contexts
&lt;/h2&gt;

&lt;p&gt;kafkactl keeps each cluster as a named context, but there is no command to add one. Contexts live in a YAML file, by default &lt;code&gt;~/.config/kafkactl/config.yml&lt;/code&gt;, which kafkactl generates on first run if it finds no config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;contexts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;local&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;brokers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;localhost:9092&lt;/span&gt;
  &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;brokers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prod-kafka-1:9092&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prod-kafka-2:9092&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then list and switch contexts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kafkactl config get-contexts
kafkactl config use-context prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If no current context is set, kafkactl uses the first context in the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everyday commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kafkactl get topics
kafkactl describe consumer-group my-group
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;describe consumer-group&lt;/code&gt; shows a snapshot of the group, including its lag. Reading and writing records looks like this, using a topic named &lt;code&gt;orders&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Read history and exit at the current end&lt;/span&gt;
kafkactl consume orders &lt;span class="nt"&gt;--from-beginning&lt;/span&gt; &lt;span class="nt"&gt;--exit&lt;/span&gt;

&lt;span class="c"&gt;# Start at a timestamp&lt;/span&gt;
kafkactl consume orders &lt;span class="nt"&gt;--from-timestamp&lt;/span&gt; 2026-09-28T00:00:00Z

&lt;span class="c"&gt;# Stop after 10 records, printed as JSON&lt;/span&gt;
kafkactl consume orders &lt;span class="nt"&gt;--from-beginning&lt;/span&gt; &lt;span class="nt"&gt;--max-messages&lt;/span&gt; 10 &lt;span class="nt"&gt;-o&lt;/span&gt; json

&lt;span class="c"&gt;# Send one keyed record with a header&lt;/span&gt;
kafkactl produce orders &lt;span class="nt"&gt;--key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;order-1 &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;hello &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt;:manual
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Coming from kcat
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;kcat&lt;/th&gt;
&lt;th&gt;kafkactl&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Follow a topic from the beginning&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kcat -b localhost:9092 -t orders -C -o beginning&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kafkactl consume orders --from-beginning&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read history and exit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kcat -b localhost:9092 -t orders -C -o beginning -e&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kafkactl consume orders --from-beginning --exit&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Print records as JSON&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kcat -b localhost:9092 -t orders -C -J&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kafkactl consume orders -o json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List topics&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kcat -b localhost:9092 -L&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kafkactl get topics&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;kafkactl reads brokers from the current context, so there is no &lt;code&gt;-b&lt;/code&gt;. Also, &lt;code&gt;kafkactl -o json&lt;/code&gt; does not follow kcat's &lt;code&gt;-J&lt;/code&gt; field layout. Before you switch a script that parses kcat output, check how kafkactl prints null values, binary payloads, and headers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two features worth knowing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes mode.&lt;/strong&gt; If your brokers are reachable only from inside a Kubernetes cluster, kafkactl can &lt;a href="https://github.com/deviceinsight/kafkactl#running-in-kubernetes" rel="noopener noreferrer"&gt;run each command in a pod&lt;/a&gt; through &lt;code&gt;kubectl&lt;/code&gt;. You keep typing the same commands on your laptop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Passwords in the OS keyring.&lt;/strong&gt; If a required credential is missing from the config file and environment, kafkactl prompts for it in the terminal. By default, after the command succeeds, it stores the credential in Keychain on macOS, GNOME Keyring on Linux, or Credential Manager on Windows, so it does not end up in plaintext in the config file. After a password rotation, run the command with &lt;code&gt;--clear-keyring&lt;/code&gt; to clear the stored credential and be prompted again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check these two gaps first
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Protobuf decoding needs local files.&lt;/strong&gt; kafkactl decodes Avro and JSON Schema through Schema Registry, but Protobuf needs local &lt;code&gt;.proto&lt;/code&gt; files or a protoset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS MSK IAM needs a plugin.&lt;/strong&gt; It works through the separate &lt;a href="https://github.com/deviceinsight/kafkactl-plugins/blob/main/aws/README.adoc" rel="noopener noreferrer"&gt;AWS plugin&lt;/a&gt; rather than out of the box.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If either one is central to your work, kcl supports both natively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is the project active?
&lt;/h2&gt;

&lt;p&gt;As of 28 September 2026, kafkactl had shipped 8 releases in the previous 12 months, the latest being v5.20.0 on 30 July 2026, with 87 commits from 8 authors over the same period. It had 2,394 Homebrew installs in the year to that date and is licensed under Apache-2.0.&lt;/p&gt;

&lt;p&gt;One caveat: a single account merged all of its pull requests in those 12 months, so review capacity rests on one maintainer. For the same metrics across kcat, kaf, kcl, and Confluent CLI, see the &lt;a href="https://kafma.app/blog/kafka-cli-tools#github-repository-metrics" rel="noopener noreferrer"&gt;GitHub repository metrics&lt;/a&gt; in the full comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a terminal is not enough
&lt;/h2&gt;

&lt;p&gt;kafkactl is the right tool for scripts, CI jobs, and quick checks. When an investigation means reading many records and moving between topics, schemas, and consumer groups, a &lt;a href="https://kafma.app/" rel="noopener noreferrer"&gt;Kafka GUI client&lt;/a&gt; such as Kafma connects to the same clusters, including over SASL, TLS, and AWS MSK IAM, and decodes Avro, Protobuf, and JSON Schema through Schema Registry without local &lt;code&gt;.proto&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;Written by the team behind Kafma.&lt;/p&gt;

</description>
      <category>kafka</category>
      <category>cli</category>
      <category>devops</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Choosing a Python Kafka Consumer: confluent-kafka vs. kafka-python vs. aiokafka</title>
      <dc:creator>KafmaKarma</dc:creator>
      <pubDate>Mon, 17 Aug 2026 08:44:37 +0000</pubDate>
      <link>https://dev.to/getkafma/choosing-a-python-kafka-consumer-confluent-kafka-vs-kafka-python-vs-aiokafka-23c0</link>
      <guid>https://dev.to/getkafma/choosing-a-python-kafka-consumer-confluent-kafka-vs-kafka-python-vs-aiokafka-23c0</guid>
      <description>&lt;p&gt;Choosing a Python Kafka consumer is mainly about how it must fit into your application. &lt;code&gt;confluent-kafka-python&lt;/code&gt;, &lt;code&gt;kafka-python&lt;/code&gt;, and &lt;code&gt;aiokafka&lt;/code&gt; can all consume records and participate in consumer groups, but they differ in API model, packaging, Schema Registry integration, and lifecycle management.&lt;/p&gt;

&lt;p&gt;Start with the application's runtime. A synchronous worker has different needs from an asyncio service, while a restricted deployment environment may rule out native dependencies before API design even matters.&lt;/p&gt;

&lt;p&gt;Versions checked: &lt;code&gt;confluent-kafka&lt;/code&gt; 2.15.0, &lt;code&gt;kafka-python&lt;/code&gt; 3.0.10, and &lt;code&gt;aiokafka&lt;/code&gt; 0.14.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick decision
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Consumer requirement&lt;/th&gt;
&lt;th&gt;Best starting point&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Synchronous processing or built-in Schema Registry support&lt;/td&gt;
&lt;td&gt;&lt;code&gt;confluent-kafka-python&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asyncio-native consumption&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aiokafka&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pure-Python installation or PyPy support&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kafka-python&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Existing consumer with no unmet requirement&lt;/td&gt;
&lt;td&gt;Keep the current library&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For the full feature comparison, repository metrics, and producer and consumer tests, see &lt;a href="https://kafma.app/blog/kafka-python-clients" rel="noopener noreferrer"&gt;Kafka Python Clients 2026: Which Should You Choose?&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  confluent-kafka-python
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/confluentinc/confluent-kafka-python" rel="noopener noreferrer"&gt;&lt;code&gt;confluent-kafka-python&lt;/code&gt;&lt;/a&gt;, installed from PyPI as &lt;code&gt;confluent-kafka&lt;/code&gt;, uses the native &lt;code&gt;librdkafka&lt;/code&gt; client. Its established synchronous Consumer API reads records through &lt;code&gt;poll()&lt;/code&gt;, giving the application direct control over polling, processing, and offset commits.&lt;/p&gt;

&lt;p&gt;It is a strong default for synchronous consumers and applications that deserialize Avro, Protobuf, or JSON Schema through Schema Registry. Newer AsyncIO APIs are also available, although applications should evaluate them separately from the long-established synchronous API.&lt;/p&gt;

&lt;p&gt;Prebuilt wheels cover common Python, operating-system, and CPU combinations. Test installation in the production container or host because less common targets may require a source build.&lt;/p&gt;

&lt;h2&gt;
  
  
  kafka-python
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://kafka-python.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;&lt;code&gt;kafka-python&lt;/code&gt;&lt;/a&gt; provides the synchronous &lt;code&gt;KafkaConsumer&lt;/code&gt;. Applications can iterate over records or call &lt;code&gt;poll()&lt;/code&gt; to receive batches, and the 3.x line supports cooperative rebalancing.&lt;/p&gt;

&lt;p&gt;Its base package is implemented in Python and distributed as a universal wheel. This makes it useful when the deployment must avoid a native Kafka library or support PyPy. Schema Registry support is not built in, so schema-aware consumers need separate deserialization tooling.&lt;/p&gt;

&lt;p&gt;Choose &lt;code&gt;kafka-python&lt;/code&gt; when packaging is the deciding requirement or when an existing synchronous consumer already runs reliably. Switching libraries without a missing capability usually adds risk without changing the application's result.&lt;/p&gt;

&lt;h2&gt;
  
  
  aiokafka
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://aiokafka.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;&lt;code&gt;aiokafka&lt;/code&gt;&lt;/a&gt; provides the asyncio-native &lt;code&gt;AIOKafkaConsumer&lt;/code&gt;. Startup and shutdown use coroutines, and applications can process records with async iteration without wrapping a synchronous consumer in an executor.&lt;/p&gt;

&lt;p&gt;This model fits FastAPI services and other applications that already manage network I/O and cancellation through an event loop. It does not include Schema Registry integration, and blocking processing must still be moved off the event loop.&lt;/p&gt;

&lt;p&gt;Choose &lt;code&gt;aiokafka&lt;/code&gt; when the complete consumer lifecycle needs to use async and await. Using it only because async syntax looks familiar is not enough if the rest of the processing path remains synchronous.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before changing a consumer library
&lt;/h2&gt;

&lt;p&gt;A consumer migration can change offset commits, retry behavior, partition assignment, rebalancing, and shutdown. Test those behaviors instead of checking only whether the new consumer receives a record.&lt;/p&gt;

&lt;p&gt;Use a separate consumer group while comparing implementations. Verify deserialized keys, values, and headers, then test restart behavior and partition revocation before moving production traffic. Two consumers in the same group will divide partitions; they will not both receive every record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final recommendation
&lt;/h2&gt;

&lt;p&gt;Start with &lt;code&gt;confluent-kafka-python&lt;/code&gt; for a new synchronous consumer or built-in Schema Registry deserialization. Choose &lt;code&gt;aiokafka&lt;/code&gt; when the application is asyncio-native. Choose &lt;code&gt;kafka-python&lt;/code&gt; when pure-Python packaging or PyPy support is the deciding requirement.&lt;/p&gt;

&lt;p&gt;Written by the team behind &lt;a href="https://kafma.app/" rel="noopener noreferrer"&gt;Kafma, a desktop Kafka UI&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kafka</category>
      <category>python</category>
      <category>programming</category>
      <category>backend</category>
    </item>
    <item>
      <title>A New Kafka Viewer for a Better Debugging Workflow</title>
      <dc:creator>KafmaKarma</dc:creator>
      <pubDate>Fri, 07 Aug 2026 01:40:40 +0000</pubDate>
      <link>https://dev.to/getkafma/a-new-kafka-viewer-for-a-better-debugging-workflow-2fhh</link>
      <guid>https://dev.to/getkafma/a-new-kafka-viewer-for-a-better-debugging-workflow-2fhh</guid>
      <description>&lt;p&gt;Kafma is a new &lt;a href="https://kafma.app/" rel="noopener noreferrer"&gt;Kafka UI client&lt;/a&gt; for macOS, Windows, and Linux, built to keep the debugging workflow in one workspace. Each topic opens as a persistent Console tab that keeps its message stream, producer, topic details, schema context, and consumer state together.&lt;/p&gt;

&lt;p&gt;A Kafka browser can help you find and read a record, but debugging usually continues beyond it. You need to inspect its metadata and schema, check what a consumer group has processed, reproduce the input, and observe what happens next.&lt;/p&gt;

&lt;p&gt;Each step is straightforward on its own. The friction comes from moving between separate consumer, producer, Schema Registry, topic, and consumer-group views and rebuilding the same context each time. Kafma keeps those parts of the investigation together, so the debugging loop becomes shorter: find the records, inspect their context, create a controlled test, and watch the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kafka debugging loses context easily
&lt;/h2&gt;

&lt;p&gt;Consider a common debugging sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Consume a bounded set of records around the time an issue occurred.&lt;/li&gt;
&lt;li&gt;Find the relevant key or payload and inspect its headers, partition, offset, and timestamp.&lt;/li&gt;
&lt;li&gt;Resolve the schema and check whether the record was encoded as expected.&lt;/li&gt;
&lt;li&gt;Check the consumer group’s position and whether the record is still in its backlog.&lt;/li&gt;
&lt;li&gt;Reuse the record as a test case or produce a modified version.&lt;/li&gt;
&lt;li&gt;Watch the new input arrive, then check whether the target consumer group processes it and advances its committed position.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is one investigation, but it is often spread across several commands and screens. Copying only the payload between them is not enough: message keys, headers, schemas, partitions, and consumer positions are all part of the context.&lt;/p&gt;

&lt;p&gt;The missing piece is not another isolated view. It is a workspace that keeps the message, schema context, topic details, consumer state, and producer together throughout the investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  A persistent workspace for each topic
&lt;/h2&gt;

&lt;p&gt;In the Console, each topic opens in its own tab. The message stream occupies the upper part of the tab, the producer stays available below it, and a topic-details drawer opens beside them. Several topic tabs can remain open at once, so one investigation does not need to replace another.&lt;/p&gt;

&lt;p&gt;The Console also remembers the open tabs for each cluster connection. If you switch from a production cluster to a local cluster and later return, the topics you were investigating are still open. Each cluster has its own tab set, which helps prevent local and production work from being mixed together while keeping both workspaces ready.&lt;/p&gt;

&lt;p&gt;This matters when a problem crosses environments: you can keep the source cluster’s context intact while reproducing the issue in a local cluster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7aio22hmae7o9nij9aid.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7aio22hmae7o9nij9aid.png" alt="Kafma topic tabs with an expanded Avro record and the topic details drawer" width="800" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Find and inspect the relevant messages
&lt;/h2&gt;

&lt;p&gt;A busy topic may contain far more data than one investigation needs. Start with a bounded snapshot from the newest records, the beginning, a specific offset, or a timestamp. Then narrow the loaded records by key, value, or partition.&lt;/p&gt;

&lt;p&gt;Kafma reads these records directly from the brokers. Opening or refreshing a Console tab does not join an application consumer group, trigger a rebalance, or commit offsets, so inspection does not change the consumer state you are trying to understand.&lt;/p&gt;

&lt;p&gt;Expand a record to see its key, value, headers, partition, offset, timestamp, size, and compression. When Schema Registry is connected, Kafma resolves the schema ID and automatically decodes Avro, Protobuf, and JSON Schema records. The original bytes remain available in the Raw view, which is useful when the serializer, schema, or wire format may be part of the problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhisudj93njqt59r0282w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhisudj93njqt59r0282w.png" alt="Kafma Console consume modes and a decoded Avro message with its metadata" width="800" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the individual controls and decoding behavior, see &lt;a href="https://kafma.app/docs/console/consuming-messages" rel="noopener noreferrer"&gt;Consuming messages&lt;/a&gt; and &lt;a href="https://kafma.app/docs/console/decoding-messages" rel="noopener noreferrer"&gt;Decoding Avro and Protobuf messages&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect the topic without leaving the Console
&lt;/h2&gt;

&lt;p&gt;A suspicious record often raises questions about the topic around it. Is a partition unhealthy? What is the retention policy? Which consumer groups use the topic? How much data is currently retained?&lt;/p&gt;

&lt;p&gt;The topic-details drawer answers those questions without replacing the message stream. It shows topic health, message count, retention, partitions, and the consumer groups associated with the topic. This keeps the topic details, selected record, and producer visible in the same tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm9ttv2zqmwjmz0rq5zk2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm9ttv2zqmwjmz0rq5zk2.png" alt="Kafka topic details showing health, message count, partitions, retention, and consumer group" width="799" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keeping the details beside the stream avoids a common break in the debugging flow: navigating to a separate topic or consumer page, finding the same resource again, and then returning to the original message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch a consumer group in the message stream
&lt;/h2&gt;

&lt;p&gt;Lag is more useful when you can connect it to the records waiting to be processed. In &lt;strong&gt;Watch Group&lt;/strong&gt; mode, the Console shows a bounded window around a selected consumer group’s committed positions and marks records as consumed, pending, or unassigned.&lt;/p&gt;

&lt;p&gt;The same view includes member status, total lag, and the lag trend. A divider separates the remaining backlog from the most recently consumed record, so you can inspect the messages at the front of the lag rather than looking at a number in isolation.&lt;/p&gt;

&lt;p&gt;Kafma reads the group’s committed positions without joining the group. Watching it therefore does not add a member or trigger a rebalance in the application you are debugging.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj4ugu49e1rvyp6j64ggz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj4ugu49e1rvyp6j64ggz.png" alt="Kafma Watch Group mode showing consumer lag and consumed and pending Kafka records" width="800" height="701"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This closes an important gap in the workflow: the message contents and the consumer’s progress are visible in the same investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reuse messages across topics and clusters
&lt;/h2&gt;

&lt;p&gt;Once you find a representative record, you should not have to reconstruct it by hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replay to topic&lt;/strong&gt; opens a producer for the current topic or another topic in the same cluster and fills in the record’s key, value, and headers. The result is an editable draft, not an immediate write. You can change the payload, choose a partition or schema version, and review everything before producing it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjujt65fs9hmt4j5qwbbd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjujt65fs9hmt4j5qwbbd.png" alt="Kafma message actions menu selecting a destination Kafka topic for replay" width="799" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a quick cross-cluster test, &lt;strong&gt;Forward to cluster&lt;/strong&gt; sends one record or the currently loaded, filtered set to the same topic name on another cluster. Kafma blocks forwarding to clusters marked as Production. Schema-backed records are forwarded as decoded JSON or text rather than retaining their original Schema Registry encoding. Export is also available when the current records need to be saved as JSON or CSV for a test or shared investigation.&lt;/p&gt;

&lt;p&gt;Replay and forwarding solve different problems. Replay creates a controlled, editable test case; forwarding moves messages directly. Forwarding does not copy Schema Registry subjects or topic configuration. When the destination needs the topic’s schemas, configuration, and a selected range of source data, use &lt;a href="https://kafma.app/docs/data-clone/overview" rel="noopener noreferrer"&gt;Data Clone&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://kafma.app/docs/console/replay-forward-and-export" rel="noopener noreferrer"&gt;Replay and export Kafka messages&lt;/a&gt; for the exact behavior of each action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep a producer loop running while you debug
&lt;/h2&gt;

&lt;p&gt;Some consumer behavior is difficult to test with a single record. An application may aggregate a stream, process periodic events, or fail only after several inputs arrive.&lt;/p&gt;

&lt;p&gt;The Console producer can send one message manually or run on a timed &lt;strong&gt;Loop&lt;/strong&gt;. A loop can repeat the current key, value, and headers at a fixed interval. If the key or value is bound to a Schema Registry subject, the loop can regenerate that field on every tick with a fresh schema-compatible payload.&lt;/p&gt;

&lt;p&gt;The loop continues while you switch to other Console tabs or pages in the same cluster. You can keep generating test traffic while inspecting another topic, checking a consumer group, or following a different part of the debugging flow. Return to the source tab to see the sent count or stop the loop. Closing the source tab stops the loop.&lt;/p&gt;

&lt;p&gt;This makes Loop useful for simulating a producer that reports continuously while you work on the consumer side. It is intended for repeatable functional testing, not throughput benchmarking.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://kafma.app/docs/console/producing-messages" rel="noopener noreferrer"&gt;Producing messages&lt;/a&gt; for payload encodings, schema generation, partitions, acknowledgments, and Loop controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  More than a Kafka viewer
&lt;/h2&gt;

&lt;p&gt;The value of one workspace is not simply that several controls fit on one screen. It is that the investigation remains intact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The topic stays open as a tab.&lt;/li&gt;
&lt;li&gt;The message stream and producer share the same context.&lt;/li&gt;
&lt;li&gt;Topic details and consumer state are available without leaving the Console.&lt;/li&gt;
&lt;li&gt;A producer loop can continue while you inspect other resources.&lt;/li&gt;
&lt;li&gt;Each cluster preserves its own set of open investigations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A conventional Kafka viewer can show which records are in a topic. For debugging, the useful unit is larger: Kafma keeps the topic, message metadata, schema context, consumer state, producer, and active investigation together even when you switch between clusters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kafma.app/download" rel="noopener noreferrer"&gt;Download Kafma&lt;/a&gt; for macOS, Windows, or Linux, or read the &lt;a href="https://kafma.app/docs/console/overview" rel="noopener noreferrer"&gt;Console documentation&lt;/a&gt; for a complete reference.&lt;/p&gt;

</description>
      <category>kafka</category>
      <category>showdev</category>
      <category>devtools</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Introducing Kafma: A Desktop Kafka UI for Faster Debugging</title>
      <dc:creator>KafmaKarma</dc:creator>
      <pubDate>Sat, 01 Aug 2026 13:55:37 +0000</pubDate>
      <link>https://dev.to/getkafma/introducing-kafma-a-desktop-kafka-ui-for-faster-debugging-1kkj</link>
      <guid>https://dev.to/getkafma/introducing-kafma-a-desktop-kafka-ui-for-faster-debugging-1kkj</guid>
      <description>&lt;p&gt;&lt;a href="https://kafma.app?utm_source=dev.to&amp;amp;utm_campaign=kafma_intro"&gt;Kafma&lt;/a&gt; is a desktop Kafka GUI client and IDE for macOS, Windows, and Linux. It keeps the consume–inspect–produce loop in one workspace and lets you clone remote topics—with their configurations, schemas, and a selected range of messages—into a local cluster for testing, with optional field masking.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I'm the creator of Kafma. It is closed-source software with Free and Pro plans.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Debug Kafka in one workspace
&lt;/h2&gt;

&lt;p&gt;A topic opens as a tab in Kafma. Messages appear at the top, while the producer stays open at the bottom. You can send a message, inspect the result, change the payload, and send it again without leaving the topic tab.&lt;/p&gt;

&lt;p&gt;Each topic keeps its own state, so several investigations can remain open at the same time. &lt;strong&gt;Live&lt;/strong&gt; tails new messages as they arrive. &lt;strong&gt;Watch Group&lt;/strong&gt; opens a bounded window around a consumer group's committed positions, showing consumed and pending records alongside member status, lag, and backlog.&lt;/p&gt;

&lt;p&gt;Kafma automatically decodes Avro and Protobuf records using the configured Schema Registry. Expand a record to inspect its decoded key and value, headers, timestamp, partition, offset, and schema metadata. From there, you can replay it to a topic in the same cluster. Pro also supports forwarding records to another cluster and exporting results as JSON or CSV.&lt;/p&gt;

&lt;p&gt;The producer handles JSON, text, Avro, and Protobuf. It can send a single record or run on a timed loop. With schema-based auto-generation enabled, each interval creates a fresh schema-compatible payload. That makes it possible to exercise a consumer and inspect the results from the same topic tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi6yy5g59cycesym0o3dz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi6yy5g59cycesym0o3dz.png" alt="Kafma producing and consuming Kafka messages in the same topic tab" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Clone remote topics for local testing
&lt;/h2&gt;

&lt;p&gt;Some failures depend on the configuration, schemas, and records in a remote environment. Recreating that context locally often requires exports, scripts, and manual setup.&lt;/p&gt;

&lt;p&gt;With Pro, Kafma's &lt;strong&gt;Data Clone&lt;/strong&gt; makes a one-time, bounded copy between connected clusters. The Free plan supports same-cluster clones. Select one or more topics, choose a time window or offset range, and copy the topic structure, the schema versions required by the selected records, and the records themselves. You can also copy structure and schemas without data when you want to produce your own test records.&lt;/p&gt;

&lt;p&gt;This is intended for focused debugging and test setup, not continuous cluster replication. Data Clone does not join a consumer group, commit offsets, or modify topics, messages, or schemas on the source. On the target, Kafma can create missing topics and register the required schemas; copied records are appended rather than overwriting existing records.&lt;/p&gt;

&lt;p&gt;Optional masking rules transform selected fields in schema-backed message values before records are written to the target cluster. A pre-flight plan shows the selected range, schemas, masking rules, and planned target operations before the clone begins. Together, those controls make it practical to bring the context around a remote failure into a local or test cluster without writing back to the source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz3zxjlqygf9b9c166y0f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz3zxjlqygf9b9c166y0f.png" alt="Kafma cloning a remote Kafka topic into a local cluster with field masking" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Review schema changes before publishing
&lt;/h2&gt;

&lt;p&gt;Kafma includes a Schema Registry workspace for Avro, JSON Schema, and Protobuf. When you create a new version, the selected registered version appears beside an editable draft. The diff updates as you type, making added and removed lines visible before the new version reaches the registry.&lt;/p&gt;

&lt;p&gt;You can browse version history, generate mock data from a registered schema, and validate a JSON payload against a selected version in Schema Lab. When you publish a draft, Kafma runs the Schema Registry compatibility check as part of the same workflow and shows the result alongside the changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu93r1r2xfxcckc1s3bd1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu93r1r2xfxcckc1s3bd1.png" alt="Kafma schema editor showing a draft diff against a registered version" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More than a message browser
&lt;/h2&gt;

&lt;p&gt;The same desktop app also covers the operational work around a debugging session. You can inspect consumer-group members, assignments, committed offsets, and partition lag; reset or delete offsets; and manage topics while viewing broker and topic configuration.&lt;/p&gt;

&lt;p&gt;Connections can be marked read-only to block Kafka and Schema Registry writes. Selecting Production turns on read-only mode automatically, though you can turn it off when writes are required. A Data Clone into a writable production target also requires typed confirmation. Kafma keeps a local activity log for operations performed in the app and can revert supported configuration and offset changes.&lt;/p&gt;

&lt;p&gt;Kafma connects to self-hosted Kafka, Confluent Cloud, and Amazon MSK, with support for SASL, mTLS, OAuth, and AWS IAM authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Available on macOS, Windows, and Linux
&lt;/h2&gt;

&lt;p&gt;Kafma connects directly from your device to the Kafka clusters and Schema Registries you configure. Kafka data does not pass through Kafma's servers, and the app has no accounts, analytics, or telemetry.&lt;/p&gt;

&lt;p&gt;The Free plan includes the core produce, consume, live-tail, schema-decoding, topic and consumer-group management, Schema Registry workflows, and same-cluster cloning. Pro adds unlimited clusters and tabs, cross-cluster cloning and forwarding, message export, and a higher Console message limit. &lt;a href="https://kafma.app/download?utm_source=dev.to&amp;amp;utm_campaign=kafma_intro"&gt;Download Kafma&lt;/a&gt; to get started with the Free plan, or see &lt;a href="https://kafma.app/pricing?utm_source=dev.to&amp;amp;utm_campaign=kafma_intro"&gt;pricing&lt;/a&gt; for Pro.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://kafma.app/changelog/v1.0.0?utm_source=dev.to&amp;amp;utm_campaign=kafma_intro"&gt;Kafma 1.0 changelog&lt;/a&gt; contains the complete feature list and more screenshots.&lt;/p&gt;

&lt;p&gt;What is the most frustrating part of your Kafka debugging workflow today?&lt;/p&gt;

</description>
      <category>kafka</category>
      <category>showdev</category>
      <category>devtools</category>
      <category>dataengineering</category>
    </item>
  </channel>
</rss>
