A Modern PostgreSQL JDBC Driver Built for Virtual Threads and Native Protocols

Aug 19

For over a decade, achieving high concurrency and database driver throughput in Java required adopting asynchronous reactive frameworks like R2DBC or RxJava. While reactive drivers freed thread resources, they introduced operational complexity, difficult execution stack traces, and non-linear control flows. With Java 21 virtual threads, this fundamental tradeoff changes. The new pg-java driver—a pre-release PostgreSQL driver written from scratch by Sehrope Sarkini and Claude—demonstrates a modern architectural paradigm by combining blocking-style synchronous code with a PostgreSQL-native wire protocol engine (PostgreSQL News).

PostgreSQL-First Architecture vs. Lowest Common Denominator

Traditional Java Database Connectivity (JDBC) drivers build their execution core directly around java.sql.* interfaces. Because standard JDBC is designed to support any relational database, its baseline abstractions represent a lowest-common-denominator feature set. Once those generic JDBC assumptions get baked into the execution core, database-specific optimizations and wire-protocol capabilities become difficult to expose natively.

In contrast, pg-java reverses this ordering by treating standard JDBC support as a secondary layer built on top of a PostgreSQL-native core API (PostgreSQL News).

+-------------------------------------------------------------+
|              Application Layer / Microservices              |
+------------------------------+------------------------------+
                               |
            +------------------+------------------+
            |                                     |
            v                                     v
+-----------------------+             +-----------------------+
|  JDBC Layer (java.sql) |             |   Native API Core     |
| (Connection, ResultSet|             |  (Direct Wire Engine  |
|   DataSource, XA)     |             |   & Streaming)        |
+-----------+-----------+             +-----------+-----------+
            |                                     |
            +------------------+------------------+
                               |
                               v
+-------------------------------------------------------------+
|                 PostgreSQL Wire Protocol                    |
+-------------------------------------------------------------+

By decoupling wire protocol handling from standard JDBC, pg-java allows direct access to PostgreSQL protocol capabilities without sacrificing standard API compatibility for object-relational mappers (ORMs). This layered design aligns with best practices when building reliable microservices that require standard integrations alongside high throughput.

Virtual Threads and Preventing Carrier Thread Pinning

To handle thousands of concurrent connections using straightforward blocking-style I/O, pg-java relies on Java 21 virtual threads (PostgreSQL News). However, virtual thread architectures require strict avoidance of carrier thread pinning. Pinning occurs when a virtual thread executes a blocking operation inside a synchronized block or method, locking the underlying OS carrier thread and preventing other virtual threads from scheduling.

To keep its blocking-style I/O friendly to virtual threads, pg-java avoids pinning carrier threads; the project specifically describes using java.util.concurrent.locks.ReentrantLock rather than synchronized around I/O (PostgreSQL News, source repository). The following is an illustrative pattern, not a class copied from the driver:

public final class VirtualThreadFriendlyChannel {
    private final ReentrantLock ioLock = new ReentrantLock();
    private final SocketChannel socketChannel;

    public VirtualThreadFriendlyChannel(SocketChannel socketChannel) {
        this.socketChannel = socketChannel;
    }

    public void sendCommand(byte[] commandPayload) {
        ioLock.lock();
        try {
            // Blocking write executed on Virtual Thread without carrier pinning
            ByteBuffer buffer = ByteBuffer.wrap(commandPayload);
            while (buffer.hasRemaining()) {
                socketChannel.write(buffer);
            }
        } catch (IOException e) {
            throw new RuntimeException("Socket write failed", e);
        } finally {
            ioLock.unlock();
        }
    }
}

Because ReentrantLock decouples lock acquisition from JVM object monitors, virtual threads waiting on ioLock.lock() or blocking on socketChannel.write() unmount cleanly from their carrier thread.

Native Streaming and JDBC Layering

Streaming is the core query execution primitive in pg-java. Standard JDBC drivers often buffer row datasets or require explicit fetch-size configurations to prevent memory exhaustion. The native pg-java engine streams protocol messages directly from the socket to processing consumers by default (PostgreSQL News).

When standard compatibility is required, the java.sql.* layer provides wrappers for standard abstractions, including Connection, PreparedStatement, ResultSet, DatabaseMetaData, DataSource, and XA (PostgreSQL News). For further reading on high-concurrency JVM patterns, explore our engineering notes.

Architectural Comparisons

Feature / DimensionStandard JDBC DriverReactive Driver (e.g., R2DBC)Modern pg-java Core
Concurrency ModelPlatform thread per connectionNon-blocking Event LoopsJava 21 Virtual Threads
API ParadigmBlocking java.sql.*Async Callbacks / Reactive StreamsSimple Blocking + Native Streams
Wire Protocol CoreGeneric standard JDBC coreCustom Reactive coreNative PostgreSQL core
Carrier Pinning RiskDepends on implementationN/ADriver is designed to avoid pinning during I/O

When NOT to Use This Driver

  1. Pre-Java 21 Environments: pg-java depends fundamentally on Java 21 virtual threads and modern concurrency models. Applications running on LTS Java 11 or 17 cannot use this driver.
  2. Production Systems Requiring Strict SLAs: Because pg-java is currently in pre-release status (PostgreSQL News), it should not yet be deployed to mission-critical production systems requiring enterprise vendor support.
  3. Multi-Database Agnostic Systems: If your application explicitly relies on database-agnostic abstractions without PostgreSQL-specific protocol benefits, standard JDBC drivers remain the conventional choice.

Common Pitfalls

  • Application-Level Pinning: Using pg-java on virtual threads will not prevent pinning if your application code executes driver queries inside custom synchronized methods. Use ReentrantLock across your application service layer as well.
  • Expecting Immediate 100% JDBC Compliance: A java.sql.* layer already exists, but full JDBC specification compliance remains a long-term project goal (PostgreSQL News).

Conclusion

By prioritizing a PostgreSQL-native core, building standard JDBC abstractions as a secondary layer, and preventing carrier thread pinning with ReentrantLock, pg-java provides a blueprint for modern database drivers on modern Java runtimes.

References

>