github nats-io/nats.net v3.0.0
NATS .NET v3.0.0

4 hours ago

NuGet

NATS .NET 3.0 is now stable. This release has been in the works since early this year and brings OpenTelemetry tracing and metrics, .NET 10 target, and a number of API and behavior changes refined over the preview series. Thanks to everyone who tried the previews and reported issues along the way. There are no changes since 3.0.0-preview.11.

.NET 10 Target

3.0 targets netstandard2.0, netstandard2.1, net8.0, and net10.0. net6.0 has been dropped.

OpenTelemetry

  • Add OTel metrics support #1154
  • Add OpenTelemetry package (tracing) #1172
  • Add custom span destination name formatter #1201
  • Add OTel ack/dropped metrics and collapse inbox trace tags #1194
  • Exclude NATS status frames from consumed metrics #1195
  • Fix server.port type and trace tag source #1175
  • Fix null-key tag in OpenTelemetry receive fallback #1205
  • Match OpenTelemetry subject filters without per-message allocation #1206
  • Make shared OpenTelemetry instrumentation options thread-visible #1207

API and behavior changes

  • Add message context to serialization interfaces #1082
  • Move socket connection interfaces to NATS.Client.Abstractions #1192
  • Default request-reply to Direct mode #1182
  • Add explicit subscription and consumer drain #1177
  • Deprecate SkipSubjectValidation #1180
  • Unify subscription channel overflow defaults #1181
  • Use System.Threading.Lock on NET9_0_OR_GREATER #1118
  • Clean up NET6 and optimize NETSTANDARD #1072
  • Update DI package dependencies and documentation #1075

Performance and internals

  • Optimize header handling with SearchValues #1203
  • Simplify object store base64url encoder #1199

Tests and docs

  • Add abstractions package boundary test #1197
  • Gate positive-path test connections with ConnectRetryAsync #1198
  • Fix flaky CI tests #1179
  • Remove stale net6.0 references #1196
  • Clarify AddNats vs AddNatsClient #1210

Thanks

Thanks to the community for the contributions and issue reports behind this release:

  • @colprog for the header handling optimization (#1203), the custom span destination name formatter (#1201), and requesting the explicit drain API (#1176)
  • @to11mtm for the object store base64url encoding work (#557, #565) behind the encoder simplification (#1199)
  • @thompson-tomo for the abstractions and package consolidation proposals (#851, #866) behind moving the socket interfaces to NATS.Client.Abstractions (#1192)

Upgrade notes

Details of the API and behavior changes, with the preview each first shipped in.

Target frameworks (since preview.1)

net6.0 is dropped and net10.0 added; the full set is netstandard2.0, netstandard2.1, net8.0, net10.0. Apps targeting .NET 6 or 7 keep working through the netstandard2.1 build, whose encoding hot paths were optimized in 3.0 (#1072), but .NET 8+ gets the fastest code paths.

Request-reply defaults to Direct mode (since preview.9)

NatsOpts.RequestReplyMode now defaults to NatsRequestReplyMode.Direct: replies are correlated through the connection's existing inbox subscription instead of setting up a subscription and channel per request. Semantics are unchanged, including ThrowIfNoResponders. To restore the previous behavior:

var opts = new NatsOpts { RequestReplyMode = NatsRequestReplyMode.SharedInbox };

Subscription channel overflow defaults unified (since preview.9)

All entry points (NatsConnection, NatsClient, DI builders) now share the NatsOpts defaults: pending channel capacity 16384 (up from 1024) and BoundedChannelFullMode.DropNewest. Previously NatsClient and the DI builders forced Wait, which can stall the socket read loop and get the client disconnected as a slow consumer. If a subscriber now falls behind by more than 16K messages, the newest messages are dropped and surfaced through MessageDropped instead of blocking. To restore blocking, accepting the slow consumer risk:

var opts = new NatsOpts { SubPendingChannelFullMode = BoundedChannelFullMode.Wait };

SkipSubjectValidation is obsolete (since preview.9)

The option still works but produces a compiler warning. Validation costs 0-5% on a publish microbenchmark and prevents silently misrouted messages: a subject containing a space splits into subject and reply-to tokens on the wire with no error.

Serializers can opt into message context (since preview.3)

New opt-in INatsSerializeWithContext<T>, INatsDeserializeWithContext<T>, and INatsSerializerWithContext<T> interfaces receive a NatsMsgContext (subject, reply-to, headers) during (de)serialization. Existing serializers work unchanged. One side effect: NatsHeaders no longer becomes read-only after publish, so a single NatsHeaders instance should not be shared across concurrent publishes.

Socket interfaces moved to NATS.Client.Abstractions (since preview.11)

INatsSocketConnection and INatsTlsUpgradeableSocketConnection moved to the NATS.Client.Abstractions package so custom transports can implement them without referencing Core. The namespace is unchanged and the types are forwarded, so existing code is source and binary compatible.

OpenTelemetry package (metrics since preview.8, package since preview.11)

The new NATS.Client.OpenTelemetry package adds AddNatsClientInstrumentation() extensions for both TracerProviderBuilder and MeterProviderBuilder, with options for subject filtering and custom span destination names.

using NATS.Client.OpenTelemetry;

services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddNatsClientInstrumentation(opts => opts.FilterSubjects(exclude: ["_INBOX.>"])))
    .WithMetrics(metrics => metrics
        .AddNatsClientInstrumentation());

Explicit drain (since preview.9)

INatsSub<T>.DrainAsync() drains a single subscription without disposing the connection: no new deliveries, in-flight messages fenced with a PING/PONG, channel completed.

var sub = await nats.SubscribeCoreAsync<Order>("orders.>");
// ... read from sub.Msgs ...
await sub.DrainAsync(); // in-flight messages still delivered, connection stays open

For JetStream consume loops, the opt-in DrainOnCancel consume option delivers buffered messages after cancellation so handlers can still ack; the default keeps the previous stop-immediately behavior.

var opts = new NatsJSConsumeOpts { DrainOnCancel = true };
await foreach (var msg in consumer.ConsumeAsync<Order>(opts: opts, cancellationToken: ct))
{
    await msg.AckAsync();
}
// on cancellation the loop drains: stops pulling, delivers buffered messages, then completes

DI package dependencies (since preview.2)

NATS.Extensions.Microsoft.DependencyInjection now depends on NATS.Client.Simplified instead of the all-inclusive NATS.Net, and NATS.Net now includes the DI package. If you referenced only the DI package and used JetStream, Key-Value, Object Store, or Services through its transitive dependency, add a direct NATS.Net reference (or the specific packages you use).

Full Changelog: v2.8.2...v3.0.0

Don't miss a new nats.net release

NewReleases is sending notifications on new releases.