ApConnection is a drop-in extension of the standard jnats connection that keeps a warm standby ready at all times. It holds two live sockets: an active connection that you publish and subscribe on exactly like any NATS connection, and a passive connection kept connected to a different server in the background. When the active socket fails, the passive's already-open socket is promoted to be the new active — no cold reconnect, no server-pool walk, no waiting on a TCP+TLS handshake at the worst possible moment — and a fresh passive is immediately armed on another server.
Because ApConnection extends NatsConnection, it is a Connection: everything you already do with jnats — publish, subscribe, request, dispatchers, drain — works unchanged. The active/passive machinery is invisible until failover, and the failover is invisible to your subscriptions.
- Fast failover — the standby socket is already connected and authenticated, so promotion is near-instantaneous rather than a fresh reconnect cycle.
- Distinct servers — the passive is always kept on a server other than the active, so a single server going down never takes both.
- Self-healing — both sides use infinite reconnect (
maxReconnects(-1)); after any failover a new passive is re-armed automatically, and a passive that loses its own socket reconnects on its own. - Core NATS — this is a connection-level failover extension; it needs nothing beyond core NATS.
ApOptions apOptions = ApOptions.builder(
Options.builder()
.server("nats://host-a:4222")
.server("nats://host-b:4222") // at least two servers so active and passive can differ
.connectionListener(activeListener)
.errorListener(activeErrors)
.build())
.passiveConnectionListener(passiveListener) // optional: watch the standby separately
.passiveErrorListener(passiveErrors)
.build();
try (ApConnection apc = ApConnection.connect(apOptions)) {
// use it like any jnats Connection
apc.publish("subject", "hello".getBytes());
Subscription sub = apc.subscribe("replies");
// inspect the standby
System.out.println("active -> " + apc.getServerInfo().getServerId());
System.out.println("passive -> " + apc.getPassiveServerInfo().getServerId());
}Beyond the full Connection API it inherits, ApConnection adds:
| Method | Purpose |
|---|---|
ApConnection.connect(ApOptions) |
Establish the active connection and arm the passive standby. |
switchToPassive() |
Fail over to the passive on demand — the same promotion that happens on failure, triggered by you. Throws IllegalStateException if there is no connected passive. |
getPassiveStatus() |
Current Status of the standby. |
getPassiveServerInfo() / getPassiveConnectedUrl() / getPassiveServers() |
Inspect where the standby is connected. |
passiveForceReconnect(...) |
Force the standby to reconnect (e.g. to rebalance it), without touching the active. |
passiveRTT() |
Round-trip time to the standby's server. Throws IllegalStateException if the passive isn't connected. |
close() |
Closes both the active and the passive. |
ApOptions wraps a normal jnats Options (which configures the active connection and seeds the shared server pool) and adds a few passive-side knobs:
ApOptions.Builder |
Meaning |
|---|---|
options(Options) |
Standard jnats options for the active connection. |
passiveConnectionListener(...) / passiveErrorListener(...) |
Listeners for the passive connection, so you can observe the standby independently of the active. |
activeServerPool(...) / passiveServerPool(...) |
Supply custom ApServerPools. If omitted, a pool is derived from the connection Options (see below); the passive shares the active's pool unless given its own. |
Note:
ApConnectionforcesmaxReconnects(-1)on both the active and the passive regardless of what the suppliedOptionsrequest — in an active/passive pair, one side must always keep trying so the standby is never permanently lost.
┌─────────────────────────────────────────────────────┐
│ Did ApOptions have an active ApServerPool instance? │
└─────────────────────────────────────────────────────┘
│ │
YES NO
│ │
▼ ▼
┌──────────────────────┐ ┌─────────────────────────────────────┐
│ Use that pool as the │ │ Did the user supply a server pool │
│ server pool for the │ │ in the regular connection Options? │
│ active instance │ └─────────────────────────────────────┘
└──────────────────────┘ │ │
YES NO
│ │
▼ ▼
┌───────────────────────────┐ ┌────────────────────────────────┐
│ Is that server pool an │ │ Create an ApPassiveServerPool │
│ instance of ApServerPool? │ │ with a standard NatsServerPool │
└───────────────────────────┘ │ as the delegate │
│ │ └────────────────────────────────┘
YES NO
│ │
▼ ▼
┌──────────────────────┐ ┌───────────────────────────────┐
│ Use that pool as the │ │ Create an ApPassiveServerPool │
│ server pool for the │ │ with that pool as the │
│ active instance │ │ delegate │
└──────────────────────┘ └───────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Did ApOptions have a passive ApServerPool instance? │
└─────────────────────────────────────────────────────┘
│ │
YES NO
│ │
▼ ▼
┌──────────────────────┐ ┌────────────────────────────────┐
│ Use that pool as the │ │ Use the active server pool as │
│ server pool for the │ │ resolved above for the passive │
│ passive instance │ │ instance │
└──────────────────────┘ └────────────────────────────────┘