pgjdbc/pgjdbc GitHub issues and pull requests (mirror)  
help / color / mirror / Atom feed
From: x4m (@x4m) <[email protected]>
To: pgjdbc/pgjdbc <[email protected]>
Subject: [pgjdbc/pgjdbc] PR #4036: Add DNS SRV discovery via jdbc:postgresql+srv:// URL scheme
Date: Tue, 21 Apr 2026 18:38:42 +0000
Message-ID: <[email protected]> (raw)

## Add DNS SRV discovery via `jdbc:postgresql+srv://` URL scheme

### Problem

When running a replicated PostgreSQL cluster, clients need to know the address of every node. Today the only way to express this in a JDBC connection string is a hard-coded comma-separated host list:

```
jdbc:postgresql://pg1.example.com,pg2.example.com,pg3.example.com/mydb?targetServerType=primary
```

This creates operational coupling: every time a node is added, removed, or replaced, the connection string in every application must be updated and redeployed. Managed database providers and HA tooling (Patroni, pg_auto_failover, etc.) cannot change the cluster topology without coordinating with application teams.

### Solution

DNS SRV records ([RFC 2782](https://datatracker.ietf.org/doc/html/rfc2782)) were designed exactly for this: a single name that maps to an ordered, weighted list of `(host, port)` endpoints. MongoDB adopted `mongodb+srv://` for the same reason.

This PR adds a `jdbc:postgresql+srv://` URL scheme (and a `srvhost=` connection property) that tells the driver to resolve `_postgresql._tcp.<cluster>` at connect time and feed the returned targets — sorted by priority then weight per RFC 2782 — into the existing multi-host `HostChooser` / `MultiHostChooser` pipeline:

```java
// Instead of hard-coding every node:
Connection conn = DriverManager.getConnection(
    "jdbc:postgresql+srv://cluster.example.com/mydb?targetServerType=primary",
    "user", "password");
```

DNS at the provider's side:

```
_postgresql._tcp.cluster.example.com.  SRV  10  1  5432  pg1.example.com.
_postgresql._tcp.cluster.example.com.  SRV  10  1  5432  pg2.example.com.
_postgresql._tcp.cluster.example.com.  SRV  20  1  5433  pg-replica.example.com.
```

Adding or removing a node now requires only a DNS record change — no application config or restart.

### How it integrates with the existing multi-host machinery

The SRV-resolved `HostSpec[]` is passed directly into `ConnectionFactoryImpl.openConnectionImpl()` unchanged. Every existing feature continues to work without modification:

| Feature | Works with SRV |
|---|---|
| `targetServerType=primary/secondary/...` | ✓ |
| `loadBalanceHosts=true` | ✓ |
| `hostRecheckSeconds` | ✓ |
| `GlobalHostStatusTracker` | ✓ |
| `connectTimeout` | ✓ |
| SSL / `sslmode` | ✓ |

### API

**New `PGProperty`:**

```java
PGProperty.SRV_HOST  // key: "srvhost"
```

**Supported URL forms:**

```
# +srv scheme (recommended)
jdbc:postgresql+srv://cluster.example.com/mydb?targetServerType=primary&sslmode=require

# srvhost= connection property
jdbc:postgresql:mydb?srvhost=cluster.example.com&targetServerType=primary
```

`srvhost` is mutually exclusive with an explicit host in the URL authority. Specifying both is rejected with a warning during `parseURL()`.

### Implementation

**`SRVLookup` (new class)** — resolves `_postgresql._tcp.<srvHost>` using JNDI DNS (`javax.naming.directory.DirContext`), which ships with every JDK since 1.3. No new dependencies are added.

```java
// DNS query: _postgresql._tcp.cluster.example.com  IN SRV
// JNDI returns each record as the string "priority weight port target"
// Records are sorted: priority ASC, weight DESC (RFC 2782)
HostSpec[] specs = SRVLookup.resolve("cluster.example.com");
```

**`Driver.parseURL()`** — detects the `+srv` scheme prefix before normal parsing, strips it, and records the SRV domain in `PGProperty.SRV_HOST`. Also accepts `srvhost=` as a query parameter.

**`Driver.hostSpecs()`** — if `SRV_HOST` is set, delegates to `SRVLookup.resolve()` instead of the normal comma-split logic.

### Testing

**Unit tests (no database, no DNS server):**

| Test | What it covers |
|---|---|
| `parseURLSrvScheme` | `jdbc:postgresql+srv://` sets `SRV_HOST` |
| `parseURLSrvSchemeWithUser` | `+srv` plus query params parses correctly |
| `parseURLSrvKeyword` | `srvhost=` property sets `SRV_HOST` |
| `parseURLSrvAndHostMutuallyExclusive` | returns null when both host and srvhost are given |
| `parseURLNormalUnchanged` | plain JDBC URLs are unaffected |
| `parseAndSortByPriorityAscending` | lower priority number wins |
| `parseAndSortByWeightDescendingWithinPriority` | higher weight wins within same priority |
| `parseAndSortStripsTrailingDot` | FQDN trailing dot is normalised |
| `parseAndSortMixedPriorityAndWeight` | 4-record ordering matches mmatvei.ru real data |
| `parseAndSortEmptyListThrows` | error on empty record list |

**Live DNS test (no database required):**

`testResolveSRVLive` queries four real public SRV records at `_postgresql._tcp.mmatvei.ru` and verifies RFC 2782 priority ordering end-to-end:

```
[0] pg4.mmatvei.ru:5432   (priority 96)
[1] pg3.mmatvei.ru:5432   (priority 97)
[2] pg2.mmatvei.ru:5432   (priority 99)
[3] pg.mmatvei.ru:5432    (priority 100)
```

If the system resolver has a stale negative cache, set `PGJDBC_TEST_SRV_DNS_SERVER=<nameserver-ip>` to query a specific authoritative server.

The test skips automatically when the domain is unreachable, so it never breaks an offline build.

### Prior art and related discussion

- pgjdbc issue discussing multi-host improvements: https://github.com/pgjdbc/pgjdbc/issues/1870
- pgx SRV PR (same feature for the Go driver): https://github.com/jackc/pgx/pull/2538
- Original libpq proposal (2019): https://www.postgresql.org/message-id/[email protected]...
- MongoDB `+srv` scheme for comparison: https://www.mongodb.com/docs/manual/reference/connection-string/#dns-seed-list-connection-format


view thread (3+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: github://pgjdbc/pgjdbc
  Cc: [email protected], [email protected]
  Subject: Re: [pgjdbc/pgjdbc] PR #4036: Add DNS SRV discovery via jdbc:postgresql+srv:// URL scheme
  In-Reply-To: <<[email protected]>>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox