Group inventory
ListGroupsAsync lists groups across brokers and retains the broker's GroupType,
ProtocolType, and State. Import Dekaf.Admin to call the extension on an
IAdminClient:
using Dekaf;
using Dekaf.Admin;
await using IAdminClient admin = Kafka.CreateAdminClient()
.WithBootstrapServers("localhost:9092")
.Build();
var inventory = await admin.ListGroupsAsync();
var connect = await admin.ListGroupsAsync(new ListGroupsOptions
{
Types = ["classic"],
ProtocolTypes = ["connect"]
});
var emptyConsumers = await admin.ListConsumerGroupsAsync(
new ListConsumerGroupsOptions { States = ["Empty"] });
Group type identifies the coordinator's group model. Protocol identifies the application using that model. They are separate fields:
| Group type | Protocol | Meaning |
|---|---|---|
classic | consumer | Classic consumer group |
classic | empty string | Simple consumer group with committed offsets |
consumer | consumer | Modern consumer group |
classic | connect | Kafka Connect group |
share | share | Share group |
streams | streams | Streams group |
The inventory preserves unknown future types and protocol strings unchanged. It does not coerce them into a consumer group or a fixed enum. Group IDs are case-sensitive and occur at most once in a result. Ordering is unspecified. Listing brokers concurrently is not an atomic cluster snapshot: a coordinator move can produce duplicate or stale entries. Filtering precedes deduplication so a nonmatching stale entry cannot hide a matching entry from another broker.
Filters and compatibility
States, Types, and ProtocolTypes combine with AND. Values within each list
combine with OR. A null or empty list imposes no restriction. State and type
comparisons ignore case; protocol comparisons are ordinal and case-sensitive.
ProtocolTypes = [""] selects simple groups. Empty state/type values and null
elements are rejected.
Each destination connection negotiates ListGroups independently. A nonempty
state filter requires version 4; a nonempty type filter requires version 5.
An unsupported requested filter throws KafkaException with
ErrorCode.UnsupportedVersion, including the broker and negotiated version.
Protocol filtering runs on returned results. Without a type filter, older
responses remain usable: GroupType is null before version 5 and State is
null before version 4. An unavailable field is never guessed from the protocol.
A broker error fails the operation rather than returning a partial inventory.
The existing convenience methods use the same filtering and aggregation rules:
ListConsumerGroupsAsyncselectsclassicorconsumergroups with protocolconsumeror an empty string. This corrects earlier behavior that also returned Connect, Share, Streams, and other non-consumer groups.ListShareGroupsAsyncselects typeshare.ListStreamsGroupsAsyncselects typestreams.
ListShareGroupsAsync and ListStreamsGroupsAsync require version 5 because
their group-type selection must be reliable. ListConsumerGroupsAsync also
works with brokers below version 5, such as Redpanda: those brokers have only
classic groups, so the call sends no type filter and selects by protocol alone.
Returned GroupType stays null for those rows. Use an unfiltered
ListGroupsAsync inventory to inspect any other older response. Filters are
also applied locally, protecting callers from nonmatching rows in a broker
response.
IGroupListingAdminClient is an optional capability implemented by Dekaf's
AdminClient and InMemoryAdminClient. Existing third-party IAdminClient
implementations need no new member. Calling the extension on an implementation
without this capability throws NotSupportedException.
In-memory testing
InMemoryAdminClient distinguishes consumer membership history, simple
offset-only groups, Share groups, and Streams groups created by a successful
AlterStreamsGroupOffsetsAsync. Removing the last Streams offset retains the
empty group's type until group deletion. Listing no longer presents every
in-memory consumer group as a Streams group. The fake models active groups as
Stable and inactive groups as Empty; it does not simulate broker rebalance
states or custom Classic membership. You can seed administrative snapshots with
InMemoryKafkaCluster.SetClassicGroupDescription, including Connect/custom protocols;
these appear in both inventory and classic descriptions.
See KIP-1043 for the distinction between group type and protocol.
Classic-group descriptions
DescribeClassicGroupsAsync deliberately uses Kafka's classic DescribeGroups API.
Use it to inspect classic consumers, empty/simple groups, Connect, or custom group
protocols. Continue using DescribeConsumerGroupsAsync for modern KIP-848 consumers
and its existing classic-consumer fallback.
var outcomes = await admin.DescribeClassicGroupsAsync(
["connect-workers", "classic-consumers"],
new DescribeClassicGroupsOptions
{
IncludeAuthorizedOperations = true,
TimeoutMs = 10_000
});
foreach (var outcome in outcomes.Values)
{
if (outcome.ErrorCode != Dekaf.Protocol.ErrorCode.None)
{
Console.WriteLine($"{outcome.GroupId}: {outcome.ErrorCode} {outcome.ErrorMessage}");
continue;
}
var group = outcome.Description!;
Console.WriteLine($"{group.GroupId}: {group.ProtocolType} / {group.ProtocolData} / {group.State}");
}
Independent coordinator lookups and per-coordinator requests run concurrently.
All started requests finish before an operation failure is returned.
Each requested group gets an outcome. Authorization and exhausted coordinator errors
leave successful groups available; only unresolved groups are retried. Duplicate,
null, or whitespace group IDs are rejected. Empty input performs no network work.
An omitted group in a broker response gets UnknownServerError. Caller cancellation
throws OperationCanceledException; the total deadline throws KafkaTimeoutException.
Initialization failures and non-Kafka programming errors still fail the operation.
Neither cancellation nor a deadline returns a partial dictionary.
Descriptions include coordinator ID, state, protocol strings, member/client identity,
and authorized-operation bits (null when not requested or unavailable). ProtocolData
is the selected protocol name, such as a consumer assignor or Connect protocol.
Every member retains opaque Metadata and AssignmentData bytes for application
decoders. Assignment contains decoded topic partitions only when ProtocolType
is exactly consumer; it is null for custom protocols or absent/unparseable bytes.
The read-only byte views remain valid after the call. No Connect-specific schema is
assumed. See Kafka's ClassicGroupDescription.
The extension uses optional IClassicGroupDescriptionAdminClient. Existing custom
IAdminClient implementations remain compatible and receive NotSupportedException
unless they implement the capability. InMemoryAdminClient supports offset-only
classic groups, seeded snapshots, scoped faults, cancellation, and deadlines.
SetClassicGroupDescription copies fixture bytes and derives consumer assignments
from AssignmentData; callers cannot mutate stored snapshots through their input.
Seeded snapshots describe admin fixtures only; they do not join a consumer group.
The in-memory fixture does not derive classic snapshots from its consumer-group
membership state. ListGroupsAsync reports those groups with GroupType = "consumer",
but DescribeClassicGroupsAsync returns GroupIdNotFound unless a classic snapshot
was seeded for that ID, even while the group appears in the listing. This is a
fixture limitation: seed SetClassicGroupDescription when testing classic member
metadata, assignments, or list-then-describe behavior. Seeded snapshots take
precedence in both listing and classic description; they do not simulate membership
coordination or track later membership changes.