C# SDK
The async-first C# SDK for .NET applications over TCP and HTTP, from installation to creating a client.
The Iggy SDK for C# is a modern, async-first client library for interacting with an Iggy message streaming server from your .NET applications. It supports TCP and HTTP transports. The package is available on NuGet and the source code lives on GitHub.
The SDK is built around the IIggyClient interface, which aggregates every feature (publishing, consuming, stream/topic management, users, offsets, consumer groups, and system operations). For the low-level per-call API and the full configuration reference, see the Guide. For the ergonomic, batteries-included producer/consumer abstractions, see the High-level SDK.
Installation
dotnet add package Apache.Iggy --prereleaseThe SDK targets .NET 8 and .NET 10 (net8.0 and net10.0). Stable versions on NuGet stop at 0.8.0, which predates the current wire protocol and cannot talk to a current server. The 0.9.0 line ships as prerelease versions (0.9.0-edge.x at the time of writing), so --prerelease is required.
Supported protocols
The SDK supports two transport protocols:
- TCP - binary protocol for optimal performance and lower latency (recommended). Over TCP the SDK speaks the VSR consensus framing, the only wire protocol the server accepts. See Connection semantics
- HTTP - RESTful JSON API for stateless operations.
BaseAddressmust then be a full URI such ashttp://127.0.0.1:3000(the server's default HTTP port is 3000, TCP is 8090)
Some operations are TCP-only and throw FeatureUnavailableException on HTTP: joining/leaving a consumer group, GetMeAsync, DeleteSegmentsAsync, and SendBinaryRequestAsync.
Connection semantics
Over TCP every request is wrapped in a 256-byte consensus header (Viewstamped Replication), the client registers a consensus session at login, and writes are replicated before they are acknowledged. The IIggyClient surface is unchanged. What changes is the behavior around connections, groups, and failures:
- Login binds a session.
LoginUserAsync/LoginWithPersonalAccessTokenAsyncrun the register handshake, and the session lives for as long as the connection. Logging out, being evicted, or losing the connection ends it, and the next login registers a fresh one. - Leader redirection is automatic. The client reads the cluster roster, follows the current leader, and re-checks it when a request is refused because the node stopped being primary.
GetCurrentAddress()reports the node the client currently talks to. - The client picks partitions. Balanced and message-key partitioning are resolved client-side (the message-key hash matches the Rust SDK byte for byte), and consumer-group polls round-robin over the partitions the coordinator assigned to this client.
- Consumer groups are assignment-based.
JoinConsumerGroupAsyncmakes this client a member. The assignment is synced on demand and refreshed on everyPingAsync. The TCP client pings on its own everyHeartbeatInterval(5 seconds by default), so an idle session survives the server's heartbeat verification and assignments stay fresh. - Credentials are bounds-checked locally. A username outside 3-50 bytes, a password outside 3-100 bytes, or a personal access token outside 1-255 bytes is rejected before the request is framed.
- Consumer offsets need an explicit partition.
StoreOffsetAsync/DeleteOffsetAsyncdo not accept anullpartition id under VSR. Passing one throws client-side. - Polling a topic that does not exist returns an empty poll rather than throwing. The server answers an unresolved topic with the empty-poll reply shape, so the client cannot tell it apart from a topic with no messages. Check the topic exists first if the distinction matters.
Failed requests
The SDK replays a request whenever the server says it never admitted it. Two exceptions surface to the caller:
IggyInvalidStatusCodeExceptioncarries the server status code, withFromServertelling apart a verdict the cluster reported from a failure the client raised itself.VsrRequestOutcomeUnknownExceptionmeans no server verdict arrived after the request was written (the connection was lost, the call was cancelled, or the server evicted the session mid-flight), so the cluster may or may not have committed it. The SDK will not replay it on a new session, because that would bypass server-side deduplication. Re-issuing it is the caller's decision.
The high-level abstractions handle the unknown-outcome case differently:
-
IggyConsumerrethrows it rather than swallowing it, because an auto-committing poll may already have advanced the offset. Rethrowing ends the polling loop, so catch it around the enumeration, decide whether the operation is safe to re-issue, and start consuming again:while (!token.IsCancellationRequested) { try { await foreach (var message in consumer.ReceiveAsync(token)) { Process(message); } } catch (VsrRequestOutcomeUnknownException) { // The last poll may or may not have committed. Decide whether // re-polling is safe for this workload, then resume the loop. } } -
IggyPublisherwith background sending does not retry it: the batch is reported through the message-batch-failed event (SubscribeOnMessageBatchFailed), typed so a subscriber can tell "not sent" from "possibly sent twice". Direct (non-background) sends throw it to the caller.
Creating a client
Create a client with IggyClientFactory.CreateClient, then call ConnectAsync:
using Apache.Iggy.Configuration;
using Apache.Iggy.Enums;
using Apache.Iggy.Factory;
var client = IggyClientFactory.CreateClient(new IggyClientConfigurator
{
BaseAddress = "127.0.0.1:8090",
Protocol = Protocol.Tcp
});
await client.ConnectAsync();
await client.LoginUserAsync("iggy", "iggy");Optionally, provide an ILoggerFactory for diagnostics (defaults to NullLoggerFactory.Instance):
using Microsoft.Extensions.Logging;
var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddFilter("Apache.Iggy", LogLevel.Information)
.AddConsole();
});
var client = IggyClientFactory.CreateClient(new IggyClientConfigurator
{
BaseAddress = "127.0.0.1:8090",
Protocol = Protocol.Tcp,
LoggerFactory = loggerFactory
});IggyClientConfigurator also exposes buffer sizes, TLS, the heartbeat interval, the maximum response frame size, automatic reconnection with exponential backoff (on by default), auto-login (so you can skip the explicit LoginUserAsync call), and client-side message encryption. See Client configuration for the full reference.
Quick start
These samples use the High-level SDK - the recommended way to build producers and consumers. For the equivalent low-level, per-call flow, see the Guide.
Producer
using System.Text;
using Apache.Iggy;
using Apache.Iggy.Configuration;
using Apache.Iggy.Enums;
using Apache.Iggy.Extensions;
using Apache.Iggy.Factory;
using Apache.Iggy.Messages;
var client = IggyClientFactory.CreateClient(new IggyClientConfigurator
{
BaseAddress = "127.0.0.1:8090",
Protocol = Protocol.Tcp
});
await client.ConnectAsync();
await client.LoginUserAsync("iggy", "iggy");
var publisher = client.CreatePublisherBuilder(
Identifier.String("sample-stream"),
Identifier.String("sample-topic"))
.CreateStreamIfNotExists("sample-stream")
.CreateTopicIfNotExists("sample-topic")
.Build();
await publisher.InitAsync();
for (var i = 0; i < 10; i++)
{
var payload = Encoding.UTF8.GetBytes($"message-{i}");
await publisher.SendMessagesAsync(new List<Message> { new(Guid.NewGuid(), payload) });
}
await publisher.DisposeAsync();Consumer
using System.Text;
using Apache.Iggy;
using Apache.Iggy.Configuration;
using Apache.Iggy.Consumers;
using Apache.Iggy.Enums;
using Apache.Iggy.Extensions;
using Apache.Iggy.Factory;
using Apache.Iggy.Kinds;
var client = IggyClientFactory.CreateClient(new IggyClientConfigurator
{
BaseAddress = "127.0.0.1:8090",
Protocol = Protocol.Tcp
});
await client.ConnectAsync();
await client.LoginUserAsync("iggy", "iggy");
var consumer = client.CreateConsumerBuilder(
Identifier.String("sample-stream"),
Identifier.String("sample-topic"),
Consumer.New(1))
.WithPollingStrategy(PollingStrategy.Next())
.WithAutoCommitMode(AutoCommitMode.AfterReceive)
.Build();
await consumer.InitAsync();
await foreach (var message in consumer.ReceiveAsync())
{
var payload = Encoding.UTF8.GetString(message.Message.Payload);
Console.WriteLine($"Offset {message.CurrentOffset}: {payload}");
}ReceiveAsync polls indefinitely - pass a CancellationToken or break out of the loop to stop.
Next steps
- Guide - client configuration reference and the full API surface: auth, streams, topics, partitions, publishing, consuming, offsets, consumer groups, system operations
- High-level SDK -
IggyPublisher/IggyConsumerwith background sending, retries, auto-commit, typed (de)serialization, and pooled (rented) buffers for allocation-free hot paths - Examples - producer, consumer-group, and typed-message samples, plus links to runnable projects
