Java 17+  ·  PostgreSQL  ·  Zero Dependencies

fencepost

Distributed concurrency toolkit for Java + PostgreSQL.

Three lock strategies, leader election, a PostgreSQL-backed message queue, and zero dependencies beyond org.postgresql:postgresql.

LockFactory<FencedLock> locks = Fencepost.Locks.session(dataSource).build();

locks.forName("orders").runLocked(token -> {
    externalStore.write(data, token.value());   // fencing token blocks stale writes
});

Three locks, one interface.

Pick the strategy that fits your workload. All three implement the same API.

pg

advisory

PostgreSQL advisory locks. No table setup. Simplest option - just point at a DataSource and go.

simplest no table needed holds connection
tx

session

SELECT ... FOR UPDATE with monotonic fencing tokens. Rejects stale writes from superseded holders.

fencing tokens holds connection
ttl

lease

Timestamp TTL with optional auto-renew. Releases the connection immediately - best for long-running work.

fencing tokens auto-expiry renewable

Single-leader, automatic failover.

Built on top of lease locks. One instance leads, the rest stand by. When the leader dies, a standby takes over.

1/N

single leader

Exactly one instance holds leadership at any time. Standby instances poll and take over when the lease expires or is released.

fencing tokens mutual exclusion
auto

automatic failover

If the leader crashes or loses its lease, a standby acquires leadership within roughly one lease duration. No manual intervention.

auto-renew TTL-based expiry
fn

lifecycle callbacks

React to leadership changes with onElected and onRevoked callbacks. Lock-free isLeader() check for hot paths.

onElected / onRevoked lock-free check

PostgreSQL-backed queue.

At-least-once delivery with visibility timeouts, delayed messages, and LISTEN/NOTIFY-based blocking dequeue.

ack

at-least-once

Messages stay invisible during processing. Explicit ack() deletes the message; nack() makes it visible again immediately.

ack / nack retry tracking
blk

blocking dequeue

Uses PostgreSQL LISTEN/NOTIFY to wake consumers instantly when messages arrive. Falls back to polling.

LISTEN/NOTIFY poll fallback
dly

delayed messages

Enqueue messages with a delay - they become visible only after the specified duration has elapsed.

visibility timeout
pub

publisher

Standalone publisher for when you only need to enqueue. Supports typed messages with headers and delayed delivery.

type + headers delayed delivery
tx

transactional publish

Publish messages within an existing JDBC transaction. Messages become visible only after the transaction commits.

atomic with your writes
sub

managed consumer

Continuous message processing with auto-ack/nack, configurable concurrency, and error handling. Just provide a handler.

auto ack / nack concurrency error handling

See your locks and queues live.

A built-in dashboard that streams the state of every lock and queue straight from PostgreSQL — no extra infrastructure to run.

sse

live updates

Pushes updates over Server-Sent Events, driven by PostgreSQL LISTEN/NOTIFY as messages flow — with an automatic polling fallback if the stream drops.

LISTEN/NOTIFY SSE poll fallback
obs

locks & queues at a glance

Every lock's holder, fencing token, and status — held / quiet / free — plus per-queue depth: total, visible, in-flight, and oldest-message age, with drill-down to individual messages.

locks + queues drill-down
run

zero-config server

Runs on the JDK's built-in HTTP server. new FencepostDashboard(dataSource).start(), then open localhost:3388 — no web framework, no extra dependency.

built-in server no extra deps
Grzegorz Piwowarek

Grzegorz Piwowarek

Java Champion, Oracle ACE, and WarsawJUG Leader