Go vs .NET NATS Server: Functionality Differences
Excludes clustering/routes, gateways, leaf nodes, and JetStream.
Generated 2026-02-22 by comparing golang/nats-server/server/ against src/NATS.Server/.
1. Core Server Lifecycle
Server Initialization
| Feature |
Go |
.NET |
Notes |
| NKey generation (server identity) |
Y |
Y |
Ed25519 key pair via NATS.NKeys at startup |
| System account setup |
Y |
Y |
$SYS account created; no event publishing yet (stub) |
| Config file validation on startup |
Y |
Stub |
-c flag parsed, ConfigFile stored, but no config parser |
| PID file writing |
Y |
Y |
Written on startup, deleted on shutdown |
Profiling HTTP endpoint (/debug/pprof) |
Y |
Stub |
ProfPort option exists but endpoint not implemented |
| Ports file output |
Y |
Y |
JSON ports file written to PortsFileDir on startup |
Accept Loop
| Feature |
Go |
.NET |
Notes |
| Exponential backoff on accept errors |
Y |
Y |
.NET backs off from 10ms to 1s on repeated failures |
| Config reload lock during client creation |
Y |
N |
Go holds reloadMu around createClient |
| Goroutine/task tracking (WaitGroup) |
Y |
Y |
Interlocked counter + drain with 10s timeout on shutdown |
| Callback-based error handling |
Y |
N |
Go uses errFunc callback pattern |
| Random/ephemeral port (port=0) |
Y |
Y |
Port resolved after Bind+Listen, stored in _options.Port |
Shutdown
| Feature |
Go |
.NET |
Notes |
Graceful shutdown with WaitForShutdown() |
Y |
Y |
Idempotent CAS-guarded ShutdownAsync() + blocking WaitForShutdown() |
| Close reason tracking per connection |
Y |
Y |
37-value ClosedState enum, CAS-based first-writer-wins MarkClosed() |
| Lame duck mode (stop new, drain existing) |
Y |
Y |
LameDuckShutdownAsync() with grace period + stagger-close with jitter |
| Wait for accept loop completion |
Y |
Y |
TaskCompletionSource signaled in accept loop finally |
| Flush pending data before close |
Y |
Y |
FlushAndCloseAsync() with best-effort flush, skip-flush for error conditions |
Signal Handling
| Signal |
Go |
.NET |
Notes |
| SIGINT (Ctrl+C) |
Y |
Y |
Both handle graceful shutdown |
| SIGTERM |
Y |
Y |
PosixSignalRegistration triggers ShutdownAsync() |
| SIGUSR1 (reopen logs) |
Y |
Y |
SIGUSR1 handler calls ReOpenLogFile |
| SIGUSR2 (lame duck mode) |
Y |
Y |
Triggers LameDuckShutdownAsync() |
| SIGHUP (config reload) |
Y |
Stub |
Signal registered, handler logs "not yet implemented" |
| Windows Service integration |
Y |
N |
|
2. Client / Connection Handling
Concurrency Model
| Feature |
Go |
.NET |
Notes |
| Separate read + write loops |
Y |
Y |
Channel-based RunWriteLoopAsync with QueueOutbound() |
| Write coalescing / batch flush |
Y |
Y |
Write loop drains all channel items before single FlushAsync |
| Dynamic buffer sizing (512B-64KB) |
Y |
N |
.NET delegates to System.IO.Pipelines |
| Output buffer pooling (3-tier) |
Y |
N |
Go pools at 512B, 4KB, 64KB |
Connection Types
| Type |
Go |
.NET |
Notes |
| CLIENT |
Y |
Y |
|
| ROUTER |
Y |
N |
Excluded per scope |
| GATEWAY |
Y |
N |
Excluded per scope |
| LEAF |
Y |
N |
Excluded per scope |
| SYSTEM (internal) |
Y |
N |
|
| JETSTREAM (internal) |
Y |
N |
|
| ACCOUNT (internal) |
Y |
N |
|
| WebSocket clients |
Y |
N |
|
| MQTT clients |
Y |
N |
|
Client Features
| Feature |
Go |
.NET |
Notes |
Echo suppression (echo: false) |
Y |
Y |
.NET checks echo in delivery path (NatsServer.cs:234,253) |
Verbose mode (+OK responses) |
Y |
Y |
Sends +OK after CONNECT, SUB, UNSUB, PUB when verbose:true |
| No-responders validation |
Y |
Y |
CONNECT rejects no_responders without headers; 503 HMSG on no match |
| Slow consumer detection |
Y |
Y |
Pending bytes threshold (64MB) + write deadline timeout (10s) |
| Write deadline / timeout policies |
Y |
Y |
WriteDeadline option with CancellationTokenSource.CancelAfter on flush |
| RTT measurement |
Y |
Y |
_rttStartTicks/Rtt property, computed on PONG receipt |
| Per-client trace mode |
Y |
N |
|
| Detailed close reason tracking |
Y |
Y |
37-value ClosedState enum with CAS-based MarkClosed() |
| Connection state flags (16 flags) |
Y |
Y |
7-flag ClientFlagHolder with Interlocked.Or/And |
Slow Consumer Handling
Go implements a sophisticated slow consumer detection system:
- Tracks
pendingBytes per client output buffer
- If pending exceeds
maxPending, enters stall mode (2-5ms waits)
- Total stall capped at 10ms per read cycle
- Closes with
SlowConsumerPendingBytes or SlowConsumerWriteDeadline
- Sets
isSlowConsumer flag for monitoring
.NET now implements pending bytes tracking and write deadline enforcement via Channel<ReadOnlyMemory<byte>>. Key differences from Go: no stall/retry mode (immediate close on threshold exceeded), write deadline via CancellationTokenSource.CancelAfter instead of SetWriteDeadline. IsSlowConsumer flag and server-level SlowConsumerCount stats are tracked for monitoring.
Stats Tracking
| Feature |
Go |
.NET |
Notes |
| Per-connection atomic stats |
Y |
Y |
.NET uses Interlocked for stats access |
| Per-read-cycle stat batching |
Y |
Y |
Local accumulators flushed via Interlocked.Add per read cycle |
| Per-account stats |
Y |
N |
|
| Slow consumer counters |
Y |
Y |
SlowConsumers and SlowConsumerClients incremented on detection |
3. Protocol Parsing
Parser Architecture
| Aspect |
Go |
.NET |
| Approach |
Byte-by-byte state machine (74 states) |
Two-phase: line extraction + command dispatch |
| Case handling |
Per-state character checks |
Bit-mask lowercase normalization (| 0x20) |
| Buffer strategy |
Jump-ahead optimization for payloads |
Direct size-based reads via Pipe |
| Split-buffer handling |
argBuf accumulation with scratch buffer |
State variables (_awaitingPayload, etc.) |
| Error model |
Inline error sending + error return |
Exception-based (ProtocolViolationException) |
| CRLF in payload |
Included in message buffer |
Excluded by design |
Protocol Operations
| Operation |
Go |
.NET |
Notes |
| PUB |
Y |
Y |
|
| HPUB (headers) |
Y |
Y |
|
| SUB |
Y |
Y |
|
| UNSUB |
Y |
Y |
|
| CONNECT |
Y |
Y |
|
| INFO |
Y |
Y |
|
| PING / PONG |
Y |
Y |
|
| MSG / HMSG |
Y |
Y |
|
| +OK / -ERR |
Y |
Y |
|
| RS+/RS-/RMSG (routes) |
Y |
N |
Excluded per scope |
| A+/A- (accounts) |
Y |
N |
Excluded per scope |
| LS+/LS-/LMSG (leaf) |
Y |
N |
Excluded per scope |
Protocol Parsing Gaps
| Feature |
Go |
.NET |
Notes |
| Multi-client-type command routing |
Y |
N |
Go checks c.kind to allow/reject commands |
| Protocol tracing in parser |
Y |
N |
Go calls traceInOp() per operation |
| Subject mapping (input→output) |
Y |
N |
Go transforms subjects via mapping rules |
| MIME header parsing |
Y |
N |
.NET delegates header handling to client layer |
| Message trace event initialization |
Y |
N |
|
Protocol Writing
| Aspect |
Go |
.NET |
Notes |
| INFO serialization |
Once at startup |
Every send |
.NET re-serializes JSON each time |
| MSG/HMSG construction |
Direct buffer write |
String interpolation → byte encode |
More allocations in .NET |
| Pre-encoded constants |
Y |
Y |
Both pre-encode PING/PONG/OK |
4. Subscriptions & Subject Matching
Trie Implementation
| Feature |
Go |
.NET |
Notes |
Basic trie with */> wildcards |
Y |
Y |
Core matching identical |
| Queue group support |
Y |
Y |
|
| Result caching (1024 max) |
Y |
Y |
Same limits |
plist optimization (>256 subs) |
Y |
N |
Go converts high-fanout nodes to array |
| Async cache sweep (background) |
Y |
N |
.NET sweeps inline under write lock |
| Atomic generation ID for invalidation |
Y |
N |
.NET clears cache explicitly |
| Cache eviction strategy |
Random |
First-N |
Semantic difference minimal |
Missing SubList Features
| Feature |
Go |
.NET |
Notes |
Stats() — comprehensive statistics |
Y |
N |
Matches, cache hits, inserts, removes, fanout |
HasInterest() — fast bool check |
Y |
N |
|
NumInterest() — fast count |
Y |
N |
|
ReverseMatch() — pattern→literal query |
Y |
N |
|
RemoveBatch() — efficient bulk removal |
Y |
N |
|
All() — enumerate all subscriptions |
Y |
N |
|
| Notification system (interest changes) |
Y |
N |
|
| Local/remote subscription filtering |
Y |
N |
|
| Queue weight expansion (remote subs) |
Y |
N |
|
MatchBytes() — zero-copy byte API |
Y |
N |
|
Subject Validation
| Feature |
Go |
.NET |
Notes |
| Basic validation (empty tokens, wildcards) |
Y |
Y |
|
| Literal subject check |
Y |
Y |
|
| UTF-8/null rune validation |
Y |
N |
Go has checkRunes parameter |
Collision detection (SubjectsCollide) |
Y |
N |
|
Token utilities (tokenAt, numTokens) |
Y |
N |
|
| Stack-allocated token buffer |
Y |
N |
Go uses [32]string{} on stack |
Subscription Lifecycle
| Feature |
Go |
.NET |
Notes |
| Per-account subscription limit |
Y |
N |
|
| Auto-unsubscribe on max messages |
Y |
Y |
.NET enforces at delivery time (NatsServer.cs:269-270) |
| Subscription routing propagation |
Y |
N |
For clusters |
Queue weight (qw) field |
Y |
N |
For remote queue load balancing |
5. Authentication & Authorization
Auth Mechanisms
| Mechanism |
Go |
.NET |
Notes |
| Username/password |
Y |
Y |
|
| Token |
Y |
Y |
|
| NKeys (Ed25519) |
Y |
Y |
.NET has framework but integration is basic |
| JWT validation |
Y |
N |
|
| Bcrypt password hashing |
Y |
Y |
.NET supports bcrypt ($2* prefix) with constant-time fallback |
| TLS certificate mapping |
Y |
Y |
X500DistinguishedName with full DN match and CN fallback |
| Custom auth interface |
Y |
N |
|
| External auth callout |
Y |
N |
|
| Proxy authentication |
Y |
N |
|
| Bearer tokens |
Y |
N |
|
| User revocation tracking |
Y |
N |
|
Account System
| Feature |
Go |
.NET |
Notes |
| Per-account SubList isolation |
Y |
Y |
|
| Multi-account user resolution |
Y |
N |
.NET has basic account, no resolution |
| Account exports/imports |
Y |
N |
|
| Per-account connection limits |
Y |
N |
|
| Per-account subscription limits |
Y |
N |
|
| Account JetStream limits |
Y |
N |
Excluded per scope |
Permissions
| Feature |
Go |
.NET |
Notes |
| Publish allow list |
Y |
Y |
|
| Subscribe allow list |
Y |
Y |
|
| Publish deny list |
Y |
Partial |
.NET has deny in struct but limited enforcement |
| Subscribe deny list |
Y |
Partial |
Same |
| Message-level deny filtering |
Y |
N |
Go filters at delivery time |
| Permission caching (128 entries) |
Y |
N |
|
| Response permissions (reply tracking) |
Y |
N |
Dynamic reply subject authorization |
| Permission templates (JWT) |
Y |
N |
e.g., {{name()}}, {{account-tag(...)}} |
6. Configuration
CLI Flags
| Flag |
Go |
.NET |
Notes |
-p/--port |
Y |
Y |
|
-a/--addr |
Y |
Y |
|
-n/--name (ServerName) |
Y |
Y |
|
-m/--http_port (monitoring) |
Y |
Y |
|
-c (config file) |
Y |
Stub |
Flag parsed, stored in ConfigFile, no config parser |
-D/-V/-DV (debug/trace) |
Y |
Y |
-D for debug, -V/-T for trace, -DV for both |
--tlscert/--tlskey/--tlscacert |
Y |
Y |
|
--tlsverify |
Y |
Y |
|
--http_base_path |
Y |
Y |
|
--https_port |
Y |
Y |
|
Configuration System
| Feature |
Go |
.NET |
Notes |
| Config file parsing |
Y |
N |
Go has custom conf parser with includes |
| Hot reload (SIGHUP) |
Y |
N |
|
| Config change detection |
Y |
N |
Go tracks inConfig/inCmdLine origins |
| ~450 option fields |
Y |
~54 |
.NET covers core options only |
Missing Options Categories
- Logging options: per-subsystem log control (file, rotation, syslog, trace/debug flags are implemented)
- Advanced limits (MaxSubs, MaxSubTokens, MaxPending, WriteDeadline)
- Tags/metadata
- OCSP configuration
- WebSocket/MQTT options
- Operator mode / account resolver
7. Monitoring
HTTP Endpoints
| Endpoint |
Go |
.NET |
Notes |
/healthz |
Y |
Y |
|
/varz |
Y |
Y |
|
/connz |
Y |
Y |
|
/ (root listing) |
Y |
Y |
|
/routez |
Y |
Stub |
Returns empty response |
/gatewayz |
Y |
Stub |
Returns empty response |
/leafz |
Y |
Stub |
Returns empty response |
/subz / /subscriptionsz |
Y |
Y |
Account filtering, test subject filtering, pagination, and subscription details |
/accountz |
Y |
Stub |
Returns empty response |
/accstatz |
Y |
Stub |
Returns empty response |
/jsz |
Y |
Stub |
Returns empty response |
Varz Response
| Field Category |
Go |
.NET |
Notes |
| Identity (ID, Name, Version) |
Y |
Y |
|
| Network (Host, Port, URLs) |
Y |
Y |
|
| Security (AuthRequired, TLS) |
Y |
Y |
|
| Limits (MaxConn, MaxPayload) |
Y |
Y |
|
| Timing (Start, Now, Uptime) |
Y |
Y |
|
| Runtime (Mem, CPU, Cores) |
Y |
Y |
|
| Connections (current, total) |
Y |
Y |
|
| Messages (in/out msgs/bytes) |
Y |
Y |
|
| SlowConsumer breakdown |
Y |
N |
Go tracks per connection type |
| Cluster/Gateway/Leaf blocks |
Y |
N |
Excluded per scope |
| JetStream block |
Y |
N |
Excluded per scope |
| TLS cert expiry info |
Y |
N |
|
Connz Response
| Feature |
Go |
.NET |
Notes |
| Filtering by CID, user, account |
Y |
Partial |
|
| Sorting (11 options) |
Y |
Y |
All options including ByStop, ByReason, ByRtt |
| State filtering (open/closed/all) |
Y |
Y |
`state=open |
| Closed connection tracking |
Y |
Y |
ConcurrentQueue<ClosedClient> capped at 10,000 entries |
| Pagination (offset, limit) |
Y |
Y |
|
| Subscription detail mode |
Y |
N |
|
| TLS peer certificate info |
Y |
N |
|
| JWT/IssuerKey/Tags fields |
Y |
N |
|
| MQTT client ID filtering |
Y |
N |
|
| Proxy info |
Y |
N |
|
8. TLS
TLS Modes
| Mode |
Go |
.NET |
Notes |
| No TLS |
Y |
Y |
|
| INFO-first (default NATS) |
Y |
Y |
|
| TLS-first (before INFO) |
Y |
Y |
|
| Mixed/Fallback |
Y |
Y |
|
| TLS-required |
Y |
Y |
|
TLS Features
| Feature |
Go |
.NET |
Notes |
| PEM cert/key loading |
Y |
Y |
|
| CA chain validation |
Y |
Y |
|
| Mutual TLS (client certs) |
Y |
Y |
|
| Certificate pinning (SHA256 SPKI) |
Y |
Y |
|
| TLS handshake timeout |
Y |
Y |
|
| TLS rate limiting |
Y |
Y |
Rate enforcement with refill; unit tests cover rate limiting and refill |
| First-byte peeking (0x16 detection) |
Y |
Y |
|
| Cert subject→user mapping |
Y |
Y |
X500DistinguishedName with full DN match and CN fallback |
| OCSP stapling |
Y |
N |
|
| Min TLS version control |
Y |
Y |
|
9. Logging
| Feature |
Go |
.NET |
Notes |
| Structured logging |
Partial |
Y |
.NET uses Serilog with ILogger |
| File logging with rotation |
Y |
Y |
Serilog.Sinks.File with rolling file support |
| Syslog (local and remote) |
Y |
Y |
--syslog and --remote_syslog flags via Serilog.Sinks.SyslogMessages |
| Log reopening (SIGUSR1) |
Y |
Y |
SIGUSR1 handler calls ReOpenLogFile |
| Trace mode (protocol-level) |
Y |
Y |
-V or -T flag enables trace-level logging |
| Debug mode |
Y |
Y |
-D flag enables debug-level logging |
| Per-subsystem log control |
Y |
N |
|
| Color output on TTY |
Y |
Y |
Auto-detected via Console.IsOutputRedirected, uses AnsiConsoleTheme.Code |
| Timestamp format control |
Y |
Y |
--logtime and --logtime_utc flags |
10. Ping/Pong & Keepalive
| Feature |
Go |
.NET |
Notes |
| Server-initiated PING |
Y |
Y |
|
| Configurable interval |
Y |
Y |
PingInterval option |
| Max pings out |
Y |
Y |
MaxPingsOut option |
| Stale connection close |
Y |
Y |
|
| RTT-based first PING delay |
Y |
Y |
Skips PING until FirstPongSent or 2s elapsed |
| RTT tracking |
Y |
Y |
_rttStartTicks/Rtt property, computed on PONG receipt |
| Stale connection stats |
Y |
Y |
StaleConnectionStats model, exposed in /varz |
Summary: Critical Gaps for Production Use
Resolved Since Initial Audit
The following items from the original gap list have been implemented:
- Slow consumer detection — pending bytes threshold (64MB) with write deadline enforcement
- Write coalescing / batch flush — channel-based write loop drains all items before single flush
- Verbose mode —
+OK responses for CONNECT, SUB, UNSUB, PUB when verbose:true
- No-responders validation — CONNECT rejects
no_responders without headers; 503 HMSG on no match
- File logging with rotation — Serilog.Sinks.File with rolling file support
- TLS certificate mapping — X500DistinguishedName with full DN match and CN fallback
- Protocol tracing —
-V/-T flag enables trace-level logging; -D for debug
Remaining High Priority
- Permission deny enforcement at delivery — deny lists checked at SUB/PUB time but not during message delivery
- Config file parsing — needed for production deployment (CLI stub exists)
- Hot reload — needed for zero-downtime config changes (SIGHUP stub exists)
Remaining Lower Priority
- Dynamic buffer sizing — delegated to Pipe, less optimized for long-lived connections
- JWT authentication — needed for operator mode
- OCSP support — certificate revocation checking
- Subject mapping — input→output subject transformation
- Subscription statistics — SubList has no stats collection
- Per-account limits — connections, subscriptions per account
- Reply subject tracking — dynamic response permissions
- Windows Service integration — needed for Windows deployment
- Per-subsystem log control — granular log levels per component