Learn Labs
5. Managing Apache Kafka Programmatically (AdminClient)

5.2 Design principles — understand these and every method becomes obvious

This is the single most important operational fact in the chapter:

2.1 Asynchronous and eventually consistent

"Perhaps the most important thing to understand about Kafka's AdminClient is that it is asynchronous. Each method returns immediately after delivering a request to the cluster controller, and each method returns one or more Future objects."

The wrapping hierarchy:

wrapped byJava Futurestatus / cancel / wait / run-after-completionKafka *Result objectwait for completion + HELPER METHODSe.g. CreateTopicsResulthelpers for common follow-up operations
  • wait until all topics are created
  • check each topic's status individually
  • retrieve the configuration of a specific topic after creation
Figure 5.2.1The wrapping hierarchy

⚠️ 2.2 Eventual consistency — the read-your-own-write trap

This is the single most important operational fact in the chapter:

Your appControllerOther brokerscreateTopics()Future COMPLETEScontroller state FULLY UPDATEDasync metadata propagationlistTopics()topic MISSING

The propagation arrow is the whole problem: at the moment the Future completes, a broker that is not yet aware of the new state can be the one that handles the read, so a listTopics request “may end up handled by a broker that is not up-to-date and will not contain a topic that was very recently created.” Eventually every broker will know about every topic, “but we can't guarantee exactly when.”

Figure 5.2.2This is the single most important operational fact in the chapter

"Because Kafka's propagation of metadata from the controller to the brokers is asynchronous, the Futures that AdminClient APIs return are considered complete when the controller state has been fully updated. At that point, not every broker might be aware of the new state, so a listTopics request may end up handled by a broker that is not up-to-date and will not contain a topic that was very recently created. This property is also called eventual consistency: eventually every broker will know about every topic, but we can't guarantee exactly when."

Practical rule: never write createTopics(...).get(); listTopics(...) and assert. Never write deleteTopics(...).get() and assert absence. Retry-with-backoff, or accept the ambiguity.

2.3 Which broker handles what — and why it matters when debugging

MODIFY cluster stateREAD cluster stateAdminClientyour applicationTHE CONTROLLERcreate, delete, alterANY BROKERthe LEAST-LOADED broker

Reads are “directed to the least-loaded broker (based on what the client knows).” This “shouldn't impact you as an API user, but it can be good to know in case you are seeing unexpected behavior, you notice that some operations succeed while others fail, or if you are trying to figure out why an operation is taking too long.” Writes all funnel through one node; reads spray across the cluster — so a sick controller breaks every mutation while every read still looks fine.

Figure 5.2.32.3 Which broker handles what — and why it matters when debugging

"This shouldn't impact you as an API user, but it can be good to know in case you are seeing unexpected behavior, you notice that some operations succeed while others fail, or if you are trying to figure out why an operation is taking too long."

That's the debugging hook: writes all funnel through one node (the controller), reads spray across the cluster. A sick controller breaks all mutations while all reads look fine.

2.4 Options objects

"Every method in AdminClient takes as an argument an Options object specific to that method" — listTopics → ListTopicsOptions, describeCluster → DescribeClusterOptions.

The universal setting: timeoutMs

"this controls how long the client will wait for a response from the cluster before throwing a TimeoutException. This limits the time in which your application may be blocked by an AdminClient operation."

Other examples: whether listTopics should also return internal topics; whether describeCluster should also return which operations the client is authorized to perform on the cluster.

2.5 Flat hierarchy — a deliberate, "controversial" choice

"All admin operations supported by the Apache Kafka protocol are implemented in KafkaAdminClient directly. There is no object hierarchy or namespaces. This is a bit controversial as the interface can be quite large and perhaps a bit overwhelming, but the main benefit is that if you want to know how to programmatically perform any admin operation on Kafka, you have exactly one JavaDoc to search, and your IDE autocomplete will be quite handy. You don't have to wonder whether you are just missing the right place to look. If it isn't in AdminClient, it was not implemented yet."

2.6 🚫 Never touch ZooKeeper directly

"At the time we are writing this chapter (Apache Kafka 2.5 is about to be released), most admin operations can be performed either through AdminClient or directly by modifying the cluster metadata in ZooKeeper. We highly encourage you to NEVER use ZooKeeper directly, and if you absolutely have to, report this as a bug to Apache Kafka."

The reason is a forward-compatibility argument, not a stylistic one:

The community will remove the ZooKeeper dependency (→ KRaft).

Your app talks to…What the migration costs you
ZooKeeper directlyMust be modified
AdminClientAPI remains exactly the same, “just with a different implementation inside the Kafka cluster”

AdminClient is the abstraction that makes the ZooKeeper→KRaft migration invisible to your code. That is its most underrated value.


On this page